R
RUSTY YELLOW PAGES

pubKeyword


Explanation

pub widens an item's default visibility, which is otherwise private — reachable only from the module that defines it and that module's descendants. Written alone before an item (pub fn, pub struct, pub mod, pub use, …), it removes that restriction entirely: the item is visible from anywhere that can reach the module defining it, including outside the crate, provided the module path leading to it is itself public.

Four scoped forms narrow that ceiling instead of removing it outright:

  • pub — no restriction beyond ordinary module-tree reachability.
  • pub(crate) — visible anywhere in the same crate, never outside it. The most common scoped form: it shares an item across a crate's own modules without adding it to the crate's public API.
  • pub(super) — visible to the parent module (and transitively, anywhere the parent's own visibility already reaches). Shorthand for pub(in <parent path>).
  • pub(in some::path) — visible only within some::path and that path's descendant modules. some::path must name an ancestor module of the item being marked — this form can only restrict how far down an already-reachable subtree the boundary is drawn, never grant visibility to a module that couldn't already see the item. pub(crate) and pub(super) are convenience shorthands for the two most common ancestors: the crate root, and the immediate parent.

On a struct, pub (in any form) applies per field, not automatically to the whole struct: pub struct Point { pub x: i32, y: i32 } makes the struct name itself visible while x is public and y stays private. See Visibility & privacy for the design rationale behind this kind of partial exposure — this page covers the grammar of pub and its scoped forms.

Usage examples

Making a struct and field visible outside its module

mod billing {
    pub struct Invoice {   // <- `pub`: visible outside the `billing` module
        pub total_cents: u64,
    }
}

let invoice = billing::Invoice { total_cents: 4200 };

Designing a public API

A cache crate splits its storage and eviction logic into separate modules that need to share an internal entry type — pub(crate) lets them do that without the type ever becoming part of the crate's public API.

pub mod api {                          // <- plain `pub`: visible from outside the crate entirely
    pub(crate) mod internal {          // <- `pub(crate)`: visible anywhere in this crate, not outside it
        pub(super) fn raw_encode(bytes: &[u8]) -> Vec<u8> {
            // <- `pub(super)`: visible only to `api`, the parent module
            bytes.to_vec()
        }

        pub(in crate::api) fn checksum(bytes: &[u8]) -> u32 {
            // <- `pub(in path)`: visible only within `api` and its descendants
            bytes.iter().map(|b| *b as u32).sum()
        }
    }

    pub fn encode(bytes: &[u8]) -> Vec<u8> {
        internal::raw_encode(bytes) // reachable: `api` is `internal`'s parent module
    }
}

Each scoped form draws the visibility boundary at exactly the module that needs it — pub(crate) shares internal across the crate, pub(super)/pub(in path) narrow individual functions further still — so nothing here is visible any wider than the code that actually uses it requires, matching the general shrink-visibility-first stance in the API Guidelines' future-proofing chapter.

Validating input

A Temperature type must never represent a value below absolute zero, so its field stays private and pub is applied only to the constructor and the read accessor, not the field itself.

pub struct Temperature {
    celsius: f64,          // private: no outside code can set an invalid value directly
}

impl Temperature {
    pub fn from_celsius(value: f64) -> Result<Self, &'static str> {
        // <- `pub`: the only public entry point that can construct one
        if value < -273.15 {
            return Err("temperature below absolute zero");
        }
        Ok(Temperature { celsius: value })
    }

    pub fn celsius(&self) -> f64 { // <- `pub`: read-only access, no bypass of the check above
        self.celsius
    }
}

Applying pub to the constructor and getter but not the field means "never below absolute zero" is enforced by the type itself, which is exactly what the API Guidelines' C-STRUCT-PRIVATE item recommends private fields for.

Explanation

Embedded support: Full

pub and its scoped forms mean exactly the same thing under #![no_std] — a compile-time-only visibility marker with no runtime cost either way. It's put to essentially the same use in an embedded HAL crate as in any other library: a driver exposes a small, curated pub surface (the methods a firmware author actually calls — set_high, read, configure) while the register-level bit-twiddling that implements those methods stays unmarked (private) or pub(crate), reachable only from the driver's own submodules. This split matters more than usual in embedded code specifically because register manipulation is unsafe and easy to get subtly wrong (wrong bit, wrong offset, a read-modify-write race with an interrupt) — keeping it out of the crate's public API is what lets the HAL guarantee its public methods uphold the peripheral's actual invariants, rather than letting a caller reach around them and write raw bits directly.

Usage examples

A HAL driver's public API over private register internals

pub struct Gpio {
    base: u32,
}

impl Gpio {
    pub fn set_high(&mut self, pin: u8) { // <- `pub`: the driver's public, safe-to-call API
        self.write_odr(1 << pin);
    }

    fn write_odr(&mut self, mask: u32) {
        // private: not `pub` — direct register access stays internal to the driver
        let odr = (self.base + 0x14) as *mut u32;
        unsafe { odr.write_volatile(odr.read_volatile() | mask) }
    }
}

Sharing register helpers across a driver's own submodules with `pub(crate)`

mod uart {
    pub(crate) fn baud_divisor(clock_hz: u32, baud: u32) -> u32 {
        // <- `pub(crate)`: shared between `uart`'s own submodules, never part of this crate's public API
        clock_hz / (16 * baud)
    }

    pub mod config {
        pub fn apply(clock_hz: u32, baud: u32) -> u32 {
            super::baud_divisor(clock_hz, baud) // reachable: same crate
        }
    }
}