Skip to content

lang::visibility

Three visibilities: private by default (the declaring module and its descendants), pub(package) (every module of the declaring package), and pub (the package's public API). Declared per item, per method, and per struct field; pub(crate) / pub(super) / pub(in path) are rejected (GP0038).

Packages, modules, and module descendants

Three levels of code organization, and visibility is defined against them. They are distinct, and the words are not interchangeable.

A package is the unit of distribution: one project.toml, one project id, one thing gos add pulls in. The library or application you are developing is a package. Its dependencies are other packages.

A module is a directory of source under src/. src/util/mod.gos declares module util. A single file directly under src/ - src/util.gos - declares the same module.

A module descendant is a module nested inside another. src/deep/ declares deep; src/deep/nest/ declares deep::nest, a descendant of deep. Descendancy is what the default visibility is written against, and it runs one way: deep::nest is a descendant of deep, and deep is not a descendant of deep::nest.

my-app/                    the package
  project.toml             its manifest - one per package
  src/
    main.gos               the entry file, at the package root
    util/mod.gos           module `util`
    deep/mod.gos           module `deep`
    deep/nest/mod.gos      module `deep::nest`, a descendant of `deep`

An inline mod name { ... } block declares a module too, with the same rules. Directories are the usual form; inline modules keep a small grouping in one file.

The three visibilities

An item with no annotation is private to the module that declares it and to that module's descendants. This is Rust's rule. A module's private helpers are reachable from the module itself and from anything nested inside it, and from nowhere else.

pub(package) widens that to every module of the declaring package, and no further. A dependency cannot reach it. This is the equivalent of Rust's pub(crate), and it is what internal machinery shared across a package should use.

pub makes the item part of the package's public API: reachable by anything that depends on the package. pub is a commitment, so annotate it deliberately.

// src/util/mod.gos - module `util`
fn helper() -> i64 { 41 }                       // util and its descendants
pub(package) fn shared() -> i64 { helper() + 1 } // anywhere in this package
pub fn public() -> i64 { shared() + 1 }          // this package's API
// src/main.gos - the package root
use util::{shared, public}

fn main() {
    println!("{} {}", shared(), public())
}

Naming util::helper from main.gos is GR0008: helper is private to module util, and the package root is not one of util's descendants.

Rust's other restriction forms do not exist. pub(crate), pub(super), and pub(in path) are rejected with GP0038 naming pub(package) - one restricted spelling, not four.

Direction matters

Visibility flows inward, never outward. A descendant sees its ancestors' private items; an ancestor does not see its descendants'.

mod outer {
    fn secret() -> i64 { 1 }

    mod inner {
        // `inner` is a descendant of `outer`, so `outer`'s private
        // items are in reach.
        pub fn read() -> i64 { super::secret() }
    }

    // `outer` is NOT a descendant of `inner`. A non-`pub` item of
    // `inner` cannot be named here.
    pub fn total() -> i64 { inner::read() }
}

What carries a visibility

Every named item: fn, struct, enum, trait, const, static, type alias, and mod.

Methods carry their own, declared inside the impl block. A method without pub is private to the module the impl was written in, even when the type is pub (GT0063). A public type with private helpers is the normal shape.

Struct fields carry their own too. A pub struct may keep private fields (GT0065): the type is API while its representation is not. A private field cannot be read, written, destructured, or named in a struct literal from outside - which means a struct with any private field cannot be constructed from outside the module that declares it, and the declaring module's constructor becomes the only way in.

mod money {
    pub struct Amount {
        pub currency: String,
        cents: i64,             // private: the representation
    }

    impl Amount {
        pub fn new(currency: String, cents: i64) -> Amount {
            Amount { currency: currency, cents: cents }
        }

        pub fn cents(&self) -> i64 { self.cents }

        fn normalize(&self) -> i64 { self.cents }   // private helper
    }
}

From another module, a.currency and a.cents() are reachable; a.cents, a.normalize(), and Amount { currency: .., cents: .. } are not.

A private module blocks what it contains

A pub item inside a private module is still unreachable from outside, and the module is what the diagnostic names - it is the one place a pub would unblock the path.

mod deep {
    mod nest { pub fn nested() -> i64 { 1 } }   // `nest` is private
}

fn main() { println!("{}", deep::nest::nested()) }
// error[GR0008]: module `nest` is private to module `deep`

Importing does not widen anything

A use is a spelling convenience, not an access grant. use util::helper on a private helper is reported at the reference, the same as writing util::helper() in full. Visibility is decided by where the name is used, not by where the use was written.