A Rust module goes to a therapist.
“What brings you in?” the therapist asks.
“I don’t feel visible.”
Items in a Rust module are private by default, so outside code can't call your
helpers just because it knows their names. Modules organize code into namespaces
and let you choose which items to expose with pub:
mod calculator {
pub fn add(a: i32, b: i32) -> i32 { // visible outside
a + b
}
fn helper() -> i32 { 42 } // private to this module
}
fn main() {
let sum = calculator::add(1, 2); // OK
// calculator::helper(); // ERROR: private
}
You can declare a module inline (as above) or in a separate file. At the crate
root, mod foo; (no body) tells the compiler to look for foo.rs or
foo/mod.rs next to the root file. Nested modules follow the module hierarchy:
mod bar; inside foo.rs looks for foo/bar.rs or foo/bar/mod.rs.
Marking a struct pub only makes the type public. Its fields are still
private unless you mark them individually. Enums work differently: once an enum
is public, its variants are public too. For a struct, you choose field by field:
mod config {
pub struct Settings {
pub port: u32, // public field
secret: String, // private even though Settings is pub
}
impl Settings {
pub fn new(port: u32) -> Self {
Settings { port, secret: String::new() }
}
}
}
Start with as little pub as possible, so callers don't depend on details you
may want to change later. Expose the constructors, methods, and fields they
need, and leave the rest private.
crate::foo::bar is the absolute path from the crate root.super::bar goes up one module (like .. in filesystem paths).self::bar is the current module (rarely needed).use foo::bar; brings bar into scope so you can call it without the full
path.Useful Resources
- The Rust Book on modules is the long-form reference, including how packages and crates fit in.
- The Rust Reference on visibility has the precise rules for when you need them.
pub(crate)is a useful middle ground: visible everywhere in your crate, hidden from external users.
This step doesn't compile. calculate tries to call a private function in
calculator.
Hit Run, read the error, and make the smallest change that fixes it.
calculator::add. Look at where add is declared.pub in front of the
fn keyword on the one the compiler is naming.
// Module with a function.
mod calculator {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
/// Wraps `calculator::add` so callers don't have to know which module the
/// addition lives in.
fn calculate(x: i32, y: i32) -> i32 {
calculator::add(x, y)
}
#[test]
fn test_calculate() {
assert_eq!(calculate(2, 3), 5);
}
Callers outside config should be able to construct settings and read the port
through the existing methods. Keep the stored port field private.
The implementation is already there, but this step doesn't compile. Run it and use the compiler's errors to find which declarations callers need to reach. Make only the visibility changes needed; leave the function bodies as they are.
When you want something between fully public and fully private, pub(crate)
makes an item visible everywhere in your own crate while keeping it hidden from
outside users. Use it for helpers that several modules share but that aren't
part of your public API.
Once the tests pass, temporarily replace settings.get_port() in the test with
settings.port. Read which declaration the compiler points to, then restore the
accessor call. Don't add pub to the field to silence this error.
Could you rename the stored field to listen_port without changing callers?
What if you narrowed it to u16 instead? Consider Settings::new(70000) before
answering: hiding storage does not make changes to accepted values harmless.
A type's visibility and the visibility of its fields and methods are separate. Follow one compiler error at a time, then compile again. Which declaration is named now?
Look at how the test reads the port. Does it need access to the stored field, or only to a method? The caller's needs determine what you expose.
// Module with a struct and a couple of methods.
mod config {
pub struct Settings {
port: u32,
}
impl Settings {
pub fn new(port: u32) -> Self {
Settings { port }
}
pub fn get_port(&self) -> u32 {
self.port
}
}
}
/// Builds a `config::Settings` for callers that don't live inside the `config`
/// module.
fn create_settings() -> config::Settings {
config::Settings::new(8080)
}
#[test]
fn test_create_settings() {
let settings = create_settings();
// `settings.port` would not compile from out here: the field is private
// even once `Settings` is `pub`. Go through the accessor.
assert_eq!(settings.get_port(), 8080);
}
Extra practice to explore at your own pace. These chapters do not count toward course progress.