Item 21: Minimize visibility

Rust's basic unit of visibility is the module; by default, a module's items (types, methods, constants) are private and only accessible to code in the same module and its submodules.

Code that needs to be more widely available is marked with the pub keyword, making it public to some other scope. A bare pub is the most common version, which makes the item visible to anything that's able to see the module it's in. That last detail is important; if a somecrate::somemodule module isn't visible to other code in the first place, anything that's pub inside it is still not visible.

The more-specific variants of pub are as follows, in descending order of usefulness:

  • pub(crate) is accessible anywhere within the owning crate. Another way of achieving the same effect is to have a pub item in a non-pub module of the crate, but pub(crate) allows the item to live near the code it is relevant for.
  • pub(super) is accessible to the parent module of the current module, which is occasionally useful for selectively increasing visibility in a crate that has a deep module structure.
  • pub(in <path>) is accesssible to code in <path>, which has to be a description of some ancestor module of the current module. This is even more occasionally useful for selectively increasing visibility in a crate that has an even deeper module structure.
  • pub(self) is equivalent to pub(in self) which is equivalent to not being pub. Uses for this are very obscure, such as reducing the number of special cases needed in code generation macros.

The Rust compiler will warn you if you have a code item that is private to the module, but which is not used within that module (and its submodules):

    // Private function that's been written but which is not yet used.
    fn not_used_yet(x: i32) -> i32 {
        x + 3
    }

Although the warning mentions code that is "never used", it's often really a warning that code can't be used from outside the module.

warning: function is never used: `not_used_yet`
  --> visibility/src/main.rs:46:8
   |
46 |     fn not_used_yet(x: i32) -> i32 {
   |        ^^^^^^^^^^^^
   |
   = note: `#[warn(dead_code)]` on by default
warning: 1 warning emitted

Separately from the question of how to increase visibility, is the question of when to do so. The answer: as little as possible, at least for code that's intended to be re-used as a self-contained crate (i.e. not a self-contained or experimental project that will never be re-used).

Once a crate item is public, it can't be made private again without breaking any code that uses the crate, thus necessitating a major version bump (Item 20). The converse is not true: moving a private item to be public generally only needs a minor version bump, and leaves crate users unaffected. (Read through the API compatibility guidelines and notice how many are only relevant if there are pub items in play).

This advice is by no means unique to this Item, nor unique to Rust:

  • The Rust API guideines includes advice that
  • Effective Java (3rd edition) has:
    • Item 15: Minimize the accessibility of classes and members
    • Item 16: In public classes, use accessor methods, not public fields
  • Effective C++ (2nd edition) has:
    • Item 18: Strive for class interfaces that are complete and minimal (my italics)
    • Item 20: Avoid data members in the public interface