Chapter 20

Modules and Visibility

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.

Visibility for Struct Fields

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.

Path Syntax

Useful Resources

Making Something Reachable

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.

Exercise 1 of 2
Open in Web Editor

Results

    Compiler / runtime output
    
                
    Stuck? Show a hint No spoilers, just a nudge
    1. The compiler complains about a private item with a path like calculator::add. Look at where add is declared.
    2. Functions inside a module are private by default. Add pub in front of the fn keyword on the one the compiler is naming.
    Reveal the full solution Spoiler: the complete answer
    // 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);
    }
    

    Public Type, Private Fields

    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.

    Check the Boundary

    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.

    Exercise 2 of 2
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  
      Stuck? Show a hint No spoilers, just a nudge

      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.

      Reveal the full solution Spoiler: the complete answer
      // 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);
      }
      
      Next chapter 21Parsing Structured Text and Generics

      Optional Chapters

      Extra practice to explore at your own pace. These chapters do not count toward course progress.

      ← Return to the Rust course curriculum