Rust Visibility
Visibility is the set of rules that decide who may use an item. Every item in a module is private by default.
pub opens one item at a time
pub means public: anyone can use it. Without it, an item stays hidden — open below can be called from main, closed cannot:
mod api {
pub fn open() -> &'static str { "open" }
fn closed() -> &'static str { "closed" }
}
println!("{}", api::open());pub(crate) stops at the crate border
pub(crate) means: visible inside your crate, hidden from everyone else:
mod inner {
pub(crate) fn shared() -> i32 { 7 }
}
println!("shared = {}", inner::shared());pub(super) opens only to the parent
pub(super) means: visible to the parent module only. for_parent answers to outer — main cannot call it directly, and must go through outer's own pub function:
mod outer {
pub mod child {
pub(super) fn for_parent() -> i32 { 9 }
}
pub fn ask() -> i32 { child::for_parent() }
}
println!("answer = {}", outer::ask());Every field needs its own pub
A pub struct can still have private fields. Settings and its name are pub. retries is not. So a Settings cannot be built field by field outside its module. Give it a pub constructor that fills the private field for you:
mod api {
pub struct Settings { pub name: String, retries: u32 }
impl Settings {
pub fn new(name: &str) -> Self { Settings { name: name.into(), retries: 5 } }
pub fn retries(&self) -> u32 { self.retries }
}
}
let s = api::Settings::new("web");
println!("{} retries={}", s.name, s.retries());A pub enum publishes all its variants
An enum is different from a struct. If the enum is pub, all its variants are pub. No per-variant pub is needed:
mod lights {
#[derive(Debug)]
pub enum Light { Red, Green }
}
println!("{:?}", lights::Light::Green);Children may look up; parents may not look down
Privacy faces one way. A parent cannot use a child module's private items. But a child can use items defined in its ancestors. This holds even for a private one, like secret_number here:
mod parent {
fn secret_number() -> i32 { 42 }
pub mod child {
pub fn peek() -> i32 { super::secret_number() }
}
}
println!("child sees = {}", parent::child::peek());Example
mod api {
pub struct Config { pub name: String, retries: u32 }
impl Config { pub fn new(name: &str) -> Self { Config { name: name.into(), retries: 3 } } pub fn retries(&self) -> u32 { self.retries } }
}
use api::Config;
fn main() { let c = Config::new("svc"); println!("{} retries={}", c.name, c.retries()); }Key points
- Use use path::to::Thing to bring one name into scope.
- Use {A, B} to import several names at once
- Use pub use to re-export: your users see a short, clean path while the real code stays organised inside
- Making a module pub only opens the module itself: every item inside stays private until it gets its own pub
- The outside world can look.
- But changes must go through your checked code.
- Visibility decides who may use each item, starting from private by default.
In this example, Config is a public struct inside the module api. Its name field is pub, so main reads c.name directly. The retries field has no pub, so it stays private even though the struct is public. The only way to see it is the retries() method, which returns a copy. new builds the Config with retries set to 3. The use api::Config line brings the short name Config into main. The output: svc retries=3.
Note
Private fields plus a read-only method is a common design.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A struct is marked pub. Are its fields automatically public too?