Packages · Lesson 22.4

Dependency

Source
Declare registry dependencies, which come from the package cache, beside a path dependency, which is compiled from a directory next to the program.
You'll need: Package, Visibility

Almost every program in this course has started with import Io::PrintLine;, and that import only works because the manifest lists Io as a dependency. This lesson looks at that list properly. There are two kinds of dependency — packages from the registry, and packages in a folder on your own disk — and this program uses both.

The [Dependencies] section

The lesson's Rux.toml ends with three lines:

[Dependencies]
Io = { Namespace = "Rux", Version = "*" }
Math = { Namespace = "Rux", Version = "*" }
Units = { Path = "Units" }

The key on the left of each line is the import name: the first segment of every import that reaches into that package. Io = … is what makes import Io::PrintLine; mean something. The inline table on the right says where the package comes from, and its shape decides which kind of dependency it is.

Registry dependencies

A registry dependency names a namespace and a version requirement. Io and Math are standard packages, published under the Rux namespace. They ship with the compiler, so they are already in the package cache on your machine; for any other registry package, rux install downloads a matching version into that cache once, and builds read it from there without going online again.

The requirement says which versions are acceptable:

RequirementAccepts
*any version — whichever is installed
^0.1.00.1.x, but not 0.2.0
>=1.2.0, <2.0.0a range spelt out

rux list shows what each line resolved to — here Resolved Rux/Io @ * to 0.1.0, the same for Math, and Path Units at 'Units'.

Standard packages are declared by hand.
In Rux 0.4.0, rux add Rux/Io does not work for the standard packages. Write their line into Rux.toml yourself, as every lesson in this course does: Io = { Namespace = "Rux", Version = "*" }.

Path dependencies

A path dependency names a folder holding a Rux.toml, relative to this manifest. Here it is Units/, right beside Src/:

pub const KilometresPerMile: float64 = 1.609344;

pub func ToMiles(kilometres: float64) -> float64 {
    return kilometres / KilometresPerMile;
}

Its source is compiled with the program, so an edit to Units/Src/Units.rux shows up on the next build, and nothing needs installing. The price is that a package with a path dependency cannot be published: the path means nothing on anyone else's machine.

Both kinds look the same in the code

Once declared, nothing in the source says where a package came from:

import Io::PrintLine;
import Math::{ Hypot, Round };
import Units::ToMiles;
flowchart LR
    reg[("registry")] -- "rux install<br/>(once)" --> cache[("package cache<br/>Io, Math")]
    cache -- "Namespace + Version" --> prog["Dependency<br/>Src/Main.rux"]
    folder["Units/<br/>beside Src/"] -- "Path" --> prog
    prog --> exe["Dependency.exe"]

Hypot(3.0, 4.0) from Math is the length of the long side of a right triangle — five kilometres for three east and four north — and ToMiles from Units converts it. Round(… * 100.0) / 100.0 keeps two decimal places.

The program

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

Src/Main.rux
// A package names what it needs in the `[Dependencies]` section of its `Rux.toml`. Each line's key
// is the import name, the first segment of every `import` that reaches into that package. This
// program uses both kinds of dependency there are:
//
//     Io = { Namespace = "Rux", Version = "*" }
//     Math = { Namespace = "Rux", Version = "*" }
//     Units = { Path = "Units" }
//
// A registry dependency names a namespace and a version requirement. `rux install` downloads a
// matching version into the package cache on this machine, and builds then read it from there,
// without going online again. `*` accepts any version; `^0.1.0` would accept 0.1.x but not 0.2.0,
// and `>=1.2.0, <2.0.0` spells a range out.
//
// A path dependency names a directory holding a `Rux.toml`, relative to this manifest. The source
// there is compiled with this program, edits to it are seen on the next build, and nothing needs
// installing. A package with a path dependency cannot be published, because the path means
// nothing on anyone else's machine.
//
// Once declared, the two kinds look the same in the code.
import Io::PrintLine;
import Math::{ Hypot, Round };
import Units::ToMiles;

func Main() -> int {
    // Three kilometres east, then four north: how far from the start, as the crow flies?
    let kilometres = Hypot(3.0, 4.0);
    let miles = Round(ToMiles(kilometres) * 100.0) / 100.0;

    PrintLine("straight line {} km", kilometres);
    PrintLine("which is      {} miles", miles);
    return 0;
}

Run it

cd Examples/Packages/Dependency
rux run
straight line 5.0 km
which is      3.11 miles

Common mistakes

Importing a package the manifest does not list.
Delete the Math line and the build stops before any code is checked: error: package 'Math' is not listed in [Dependencies], with the note "the import requires a package dependency with the same import name". Every import name needs its line.
A path that points nowhere.
With Path = "Unit", rux reports error: could not open the manifest for the missing folder, then error: cannot load dependency package 'Units'. The path is relative to the manifest that declares it, not to the folder you run rux from.
Leaving out the version.
Math = { Namespace = "Rux" } fails with error: registry dependency 'Math' must declare 'Version'. Write Version = "*" if any version will do.
Mixing the two kinds.
A dependency is one kind or the other. Units = { Path = "Units", Version = "*" } fails with error: path dependency 'Units' cannot also declare 'Namespace' or 'Version'.
A requirement nothing installed can meet.
With Version = "^9.0.0" for Math, the build fails with error: no installed version of 'Rux/Math' satisfies '^9.0.0', and a note lists the versions that are installed. Loosen the requirement, or install a version that matches.

Try it yourself

  1. Change the requirement for Math to ^0.1.0 and run rux list. Does anything change in the build?
  2. Add a ToKilometres function to Units and use it to convert the rounded miles back. Why is the answer not exactly 5.0?
  3. The import name does not have to match the package's own name. Rename the line to Distance = { Path = "Units", Package = "Units" } and change the import to match.

Learn more

  • Dependencies — requirement syntax, the package cache and target-specific dependencies
  • rux install and rux list in the CLI reference
  • Math — the standard package this program borrows Hypot and Round from
  • Workspace — several packages, with path dependencies between them, kept in one tree