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

Importing a private item.
A private function cannot even be imported. import Tally::{ Counter, Clamp }; fails with error: function 'Clamp' is private to package 'Tally', and the help line says "add 'pub' to the declaration of 'Clamp'". The private constant is refused the same way, as constant 'Limit' is private to package 'Tally'.
Reaching for a private field.
counter.count = 0; fails with error: struct field 'count' is private to package 'Tally'. Reading it is refused just the same. Ask the type instead, through Count().
Building a struct that has private fields.
Counter { step: 4, count: 0 } fails with error: struct 'Counter' cannot be initialized outside its package because it has private fields, and the help line says "use a public constructor instead". Call Counter(4).
Making the type public but not its methods.
pub on a struct does not reach its methods. Remove it from Tick and the call fails with error: method 'Tick' is private to package 'Tally'. Take it off the struct instead, and even the import fails: error: type 'Counter' is private to package 'Tally'.

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