The Modern Approach: 2018 Edition Path Changes

With Rust 2018, the module system was simplified. You can now use the following structure:

src/
├── main.rs
├── config.rs
└── api/
    ├── mod.rs
    ├── client.rs
    └── response.rs
// src/main.rs
mod config;
mod api;

use config::Config;
use api::Client;

fn main() {
    let config = Config::new(String::from("my-api-key"), 30);
    let client = Client::new(config);
    let response = client.send_request("/users");
    println!("Response status: {}", response.status);
}

// src/config.rs
pub struct Config {
    pub api_key: String,
    pub timeout: u32,
}

impl Config {
    pub fn new(api_key: String, timeout: u32) -> Config {
        Config { api_key, timeout }
    }
}

// src/api/mod.rs
mod client;
mod response;

pub use client::Client;
pub use response::Response;

// src/api/client.rs
use crate::config::Config;
use super::response::Response;

pub struct Client {
    config: Config,
}

impl Client {
    pub fn new(config: Config) -> Client {
        Client { config }
    }
    
    pub fn send_request(&self, endpoint: &str) -> Response {
        println!(
            "Sending request to {} with API key {} and timeout {}",
            endpoint,
            self.config.api_key,
            self.config.timeout
        );
        
        Response { status: 200, body: String::from("Success") }
    }
}

// src/api/response.rs
pub struct Response {
    pub status: u32,
    pub body: String,
}

Advanced Visibility Rules

Rust’s visibility system is more nuanced than just public and private:

Visibility Modifiers

  1. pub: Visible to all code
  2. No modifier (default): Visible only within the current module and its descendants
  3. pub(crate): Visible within the current crate
  4. pub(super): Visible within the parent module
  5. pub(in path): Visible within the specified path
mod outer {
    // Visible only within outer
    fn private_function() {
        println!("This is private to outer");
    }
    
    // Visible everywhere
    pub fn public_function() {
        println!("This is public");
        private_function(); // Can call private_function here
    }
    
    // Visible only within the current crate
    pub(crate) fn crate_visible_function() {
        println!("This is visible within the crate");
    }
    
    pub mod inner {
        // Visible only within outer
        pub(super) fn super_visible_function() {
            println!("This is visible to the parent module");
        }
        
        // Visible only within outer and its descendants
        pub(in crate::outer) fn path_visible_function() {
            println!("This is visible within the specified path");
        }
    }
}

fn main() {
    outer::public_function();
    outer::crate_visible_function();
    // outer::private_function(); // Error: private
    // outer::inner::super_visible_function(); // Error: only visible to parent
    // outer::inner::path_visible_function(); // Error: only visible within path
}

Struct Field Visibility

Struct fields have their own visibility rules:

mod data {
    pub struct User {
        pub username: String,     // Visible everywhere
        pub email: String,        // Visible everywhere
        password: String,         // Private to the module
    }
    
    impl User {
        pub fn new(username: String, email: String, password: String) -> User {
            User {
                username,
                email,
                password,
            }
        }
        
        pub fn check_password(&self, password: &str) -> bool {
            self.password == password // Can access private field here
        }
    }
}

fn main() {
    let user = data::User::new(
        String::from("alice"),
        String::from("[email protected]"),
        String::from("password123"),
    );
    
    println!("Username: {}", user.username);
    println!("Email: {}", user.email);
    // println!("Password: {}", user.password); // Error: password is private
    
    let is_valid = user.check_password("password123");
    println!("Password valid: {}", is_valid);
}

Enum Variant Visibility

Enum variants inherit the visibility of the enum:

mod status {
    pub enum Status {
        Active,
        Inactive,
        Suspended,
    }
    
    // Even with pub(crate), all variants are still accessible
    // when Status is accessible
    pub(crate) enum InternalStatus {
        Initializing,
        Processing,
        ShuttingDown,
    }
}

fn main() {
    let status = status::Status::Active;
    
    match status {
        status::Status::Active => println!("Active"),
        status::Status::Inactive => println!("Inactive"),
        status::Status::Suspended => println!("Suspended"),
    }
    
    // We can access InternalStatus because it's pub(crate)
    let internal = status::InternalStatus::Processing;
}

Re-exporting and the Facade Pattern

Re-exporting allows you to create a public API that’s different from your internal structure:

Basic Re-exporting

mod inner {
    pub fn function() {
        println!("This is a function in the inner module");
    }
    
    pub struct Data {
        pub value: i32,
    }
}

// Re-export inner::function as function
pub use inner::function;

// Re-export inner::Data as Data
pub use inner::Data;

fn main() {
    // Now we can call function directly
    function();
    
    // And use Data directly
    let data = Data { value: 42 };
    println!("Data value: {}", data.value);
}

The Facade Pattern

The facade pattern creates a simplified public API by re-exporting selected items:

// lib.rs
mod config;
mod api;
mod database;
mod utils;

// Public API
pub use config::Config;
pub use api::Client;
pub use api::Response;

// These modules and their contents remain private
// database::*
// utils::*

Users of your library only see the re-exported items, creating a clean, focused API.


Managing Dependencies with the use Keyword

The use keyword helps manage dependencies and reduce repetition: