Packages · Lesson 22.2

Visibility

Source
Use pub to choose what a library package shows to the packages that depend on it, and see a private item refused across that boundary.

In Module, nothing was marked pub and everything still worked, because every file of a package sees everything the package declares. pub matters at a different border: the one between packages. It marks what a package lets the packages that depend on it see, and everything without it stays private. That is how a library keeps a promise — here, that a counter never passes its limit — no matter what the code using it tries.

Two packages in one lesson

Showing a border between packages needs two of them. The program is the lesson's own package; the library, Tally, sits in a folder beside Src/ with its own manifest:

Visibility/
├── Rux.toml          the program, which depends on Tally
├── Src/
│   └── Main.rux
└── Tally/
    ├── Rux.toml      Type = "SourceLibrary"
    └── Src/
        └── Counter.rux

The program's Rux.toml names the library under [Dependencies]:

Tally = { Path = "Tally" }

That is a path dependency, and Dependency covers it properly. For now it is enough that one rux run compiles both packages, and that Main.rux can then write import Tally::Counter;.

pub, one declaration at a time

Visibility is chosen for each declaration separately, and a member does not inherit it from its type. In Tally/Src/Counter.rux:

pub struct Counter {
    pub step: int;
    count: int;
}

Counter is public and so is its step field, but count is not. The functions in the extend block are each marked too, and the limit and the helper that enforces it are left private:

Declaration in Tallypub?What Main.rux can do with it
struct Counteryesimport it and name the type
field stepyesread it and change it
field countnonothing — not even read it
Counter(step), Tick()yescall them
Count()yescall it, to learn the count
const Limit, Clamp(…)nonothing — not even import them

A private field keeps a promise

The library's one promise is that count never goes past Limit. Every change to it goes through Tick, and Tick clamps:

pub func Tick(self: &var Counter) {
    self.count = Clamp(self.count + self.step);
}

Because count is private, there is no other way in. The program ticks three times with a step of 4:

var counter = Counter(4);

counter.Tick();
counter.Tick();
counter.Tick();

Three ticks of 4 would make 12, but the program prints count 10. Nothing in Main.rux can push it further, because nothing in Main.rux can touch count. The public constructor is the only way to make a Counter at all: a struct literal would have to set count, so it is refused.

flowchart LR
    main["Visibility<br/>`Src/Main.rux`"]
    subgraph tally ["`package Tally`"]
        direction TB
        pubs["`pub`: `Counter`, `step`,<br/>`Counter()`, `Tick()`, `Count()`"]
        privs["private: `count`,<br/>`Limit`, `Clamp()`"]
        pubs -- "used inside<br/>the package" --> privs
    end
    main -- "allowed" --> pubs
    main -.->|"refused"| privs

This is the usual shape of a good library: a small public surface, and private details that it is free to change later without breaking anyone.

The program

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

Src/Main.rux
// Inside one package, every file sees every declaration. `pub` matters at the boundary between
// packages: it marks what a package lets the packages that depend on it see.
//
// This lesson needs two packages to show that. The companion library lives in `Tally/`, beside
// `Src/`, with its own `Rux.toml`. This package's manifest lists it under `[Dependencies]` as
// `Tally = { Path = "Tally" }`, so building this program compiles the library as well.
//
// Visibility is chosen per declaration, and a member does not inherit it from its type. `Counter`
// is public, its `step` field is public, and its `count` field is not. A caller can read `step`,
// but can learn the count only by asking `Count()`, and can change it only through `Tick()`, which
// never lets it pass the library's private limit.
import Io::PrintLine;
import Tally::Counter;

// A private function cannot even be imported. Adding it to the import above is rejected:
//
//     import Tally::{ Counter, Clamp };
//
//     error: function 'Clamp' is private to package 'Tally'
//     help: add 'pub' to the declaration of 'Clamp'

func Main() -> int {
    // The public constructor is the only way to make a `Counter` here. A struct literal would have
    // to set the private `count`, and is refused for that reason.
    var counter = Counter(4);

    counter.Tick();
    counter.Tick();
    counter.Tick();

    // Three ticks of 4 would make 12; the library's private limit keeps it at 10.
    PrintLine("step {}, count {}", counter.step, counter.Count());

    // The private field itself is out of reach, for reading as much as for writing:
    //
    //     counter.count = 0;
    //
    //     error: struct field 'count' is private to package 'Tally'
    //     help: add 'pub' before the declaration of 'count'
    return 0;
}

Run it

cd Examples/Packages/Visibility
rux run
step 4, count 10

Common mistakes

Try it yourself

  1. step is public, so the program may change it. Set counter.step = 1; before the ticks and predict the output.
  2. Add a public Reset method to Counter in Tally/Src/Counter.rux that sets count back to 0, and call it from Main.
  3. Make Limit public, import it next to Counter, and print it. Is the promise about count any weaker now?

Learn more