Packages · Lesson 22.6

Source library

Source
Write a library that is compiled into the program using it, so its generic code serves that program's own types.
You'll need: Dependency, Generic, Slice

You have already used several libraries: Tally, Units and Temperature earlier in this part, and Io in nearly every lesson before that. All of them are source libraries, Type = "SourceLibrary", the usual kind of library in Rux. This lesson looks at what that type means: a source library is never compiled on its own. Its source is compiled into each package that depends on it, and that is what lets a generic function in it work with types the library has never heard of.

Compiled into the program that uses it

The library is in Stats/, beside Src/, and the program depends on it by path:

Stats = { Path = "Stats" }

When you build the program, the compiler reads Stats/Src/Stats.rux along with the program's own Src/Main.rux, almost as if the library's files were part of the program. "Almost", because pub still guards the border between the two, exactly as in Visibility. One build, one executable:

flowchart LR
    lib["Stats/Src/Stats.rux<br/>generic Largest"] --> c["rux build<br/>(in SourceLibrary/)"]
    main["Src/Main.rux<br/>int32 and float64 calls"] --> c
    c --> exe["SourceLibrary.exe<br/>Largest for int32<br/>Largest for float64"]

So a source library produces nothing by itself — no .lib, no .dll, nothing in Bin/.

A generic function, made to order

The whole library is one generic function:

pub func Largest<T>(values: T[..]) -> T {
    var best = values[0];
    for value in values {
        if value > best {
            best = value;
        }
    }
    return best;
}

There is no single machine-code Largest anywhere, because the library is never compiled alone. Each program that depends on it compiles this source with its own and gets one Largest for every element type it actually uses. This one passes it two arrays:

let scores: int32[5] = [72, 95, 88, 61, 90];
let prices: float64[3] = [4.25, 19.99, 7.5];

PrintLine("highest score {}", Largest(scores));
PrintLine("highest price {}", Largest(prices));

so Largest is compiled twice, once with T as int32 and once with T as float64. A different program could use it for char and get a third. That is the main reason Rux libraries are shipped as source: generic code can serve the user's types.

Checked alone, never built alone

You can still work on a source library by itself. In Stats/, rux check parses and type-checks it. The commands that would make an artifact refuse:

Command in Stats/Result
rux checkChecked 1 package — the library on its own is valid
rux buildrefused: it "cannot be built or run as a top-level target"
rux runrefused: "package 'Stats' is a source library and cannot be run"

To see the library do anything, build or run a package that uses it — here, the lesson's own program.

Errors show up where the type meets the code

A generic function only promises to work for types that support what it does. Largest uses >, so it works for numbers and characters, but not for a struct that declares no >. Because the library is compiled with your program, that problem is found when your program is built — and reported inside the library's source, with a note pointing back at your call. Generic bound shows how a library can state such a requirement up front instead.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// `Type = "SourceLibrary"` is the usual kind of library in Rux, and every standard package such as
// `Io` is one. Its source is compiled into each package that depends on it, as if its files were
// part of that package, while `pub` still guards the boundary between the two.
//
// So a source library produces nothing by itself. In `Stats/`, `rux check` works, but the commands
// that would make an artifact refuse:
//
//     rux build
//     error: package 'Stats' has Type = "SourceLibrary" and is compiled into dependent packages;
//            it cannot be built or run as a top-level target
//
// The program is where it all comes together. Building this package compiles `Stats` with it, and
// that is what lets a generic library function serve types the library never heard of: `Largest`
// is compiled here for `int32` and again for `float64`, because those are what this program
// passes it.
import Io::PrintLine;
import Stats::Largest;

func Main() -> int {
    let scores: int32[5] = [72, 95, 88, 61, 90];
    let prices: float64[3] = [4.25, 19.99, 7.5];

    PrintLine("highest score {}", Largest(scores));
    PrintLine("highest price {}", Largest(prices));
    return 0;
}

Run it

cd Examples/Packages/SourceLibrary
rux run
highest score 95
highest price 19.99

Common mistakes

Building the library itself.
In Stats/, rux build fails with error: package 'Stats' has Type = "SourceLibrary" and is compiled into dependent packages; it cannot be built or run as a top-level target. Use rux check there, and build the program that depends on it.
A type the generic code cannot handle.
Pass Largest an array of a struct Point and the error is reported at value > best in Stats.rux: error: operator '>' is not defined for 'Point'. The note under it, "in 'Largest' instantiated with T = Point by the call at …", leads back to the line in your program. Strings fail the same way, as operator '>' is not defined for slice type 'char8[..]'.
An empty slice.
Largest reads values[0] before it looks at anything else. Give it an empty slice and the program stops at run time with Panic: index out of range. The library trusts its caller here; a safer version would return an optional instead.

Try it yourself

  1. Add pub func Smallest<T>(values: T[..]) -> T to Stats and print the lowest score and the lowest price.
  2. Call Largest with an array of char, such as ['r', 'u', 'x']. Which letter wins, and why?
  3. Change Largest to return T? and give back none when values.length is 0. Update the program to print a fallback with ??.

Learn more