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
pub
: Visible to all code- No modifier (default): Visible only within the current module and its descendants
pub(crate)
: Visible within the current cratepub(super)
: Visible within the parent modulepub(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: