Modules and Visibility
Module Organization in Libraries
Well-structured Rust libraries keep internal implementation modules private (mod ...;) and curate a flat, deliberate public API in src/lib.rs using pub use re-exports, logical feature submodules, and an optional prelude module.
Core Principles of Rust Library Architecture
| Principle | Implementation Technique | Benefit |
|---|---|---|
| Private implementation modules | Declare mod parser; mod buffer; (without pub) in src/lib.rs |
Freedom to rename, split, or reorganize internal files without breaking downstream users |
| Curated root re-exports | pub use crate::client::Client; in src/lib.rs |
Users import primary types directly from the crate root (use my_lib::Client;) |
Unified crate Error and Result<T> |
pub use crate::error::{Error, Result}; |
Every public function in the library returns a consistent my_lib::Result<T> |
bin + lib split for CLI apps |
Put core logic in src/lib.rs and keep src/main.rs as a thin CLI wrapper |
Integration tests in tests/ and benchmarks in benches/ can import src/lib.rs directly |
Recommended Library Crate Structure (`src/`):
src/
├── lib.rs <-- Public facade: `mod client; mod error; pub use client::Client; ...`
├── main.rs <-- (Optional) Thin CLI binary calling `my_crate::run()`
├── error.rs <-- `pub enum Error` + `pub type Result<T> = std::result::Result<T, Error>;`
├── client.rs <-- Core implementation (`pub struct Client`)
└── codec/ <-- Internal subsystem (`mod codec;` is private to external users)
├── frame.rs <-- Uses `pub(crate)` to share types with `client.rs`
└── varint.rs
Example: Clean src/lib.rs Facade Over Private Submodules
// --- Internal private modules (not exposed in public docs as module paths) ---
mod error {
use std::fmt;
#[derive(Debug, PartialEq)]
pub enum Error {
EmptyKey,
QuotaExceeded { limit: usize },
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyKey => write!(f, "key must not be empty"),
Self::QuotaExceeded { limit } => write!(f, "quota of {limit} items exceeded"),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
}
mod store {
use crate::error::{Error, Result};
use std::collections::HashMap;
pub struct KvStore {
entries: HashMap<String, String>,
max_entries: usize,
}
impl KvStore {
pub fn with_Quota(max_entries: usize) -> Self {
Self {
entries: HashMap::new(),
max_entries,
}
}
pub fn insert(&mut self, key: &str, value: &str) -> Result<()> {
if key.is_empty() {
return Err(Error::EmptyKey);
}
if !self.entries.contains_key(key) && self.entries.len() >= self.max_entries {
return Err(Error::QuotaExceeded {
limit: self.max_entries,
});
}
self.entries.insert(String::from(key), String::from(value));
Ok(())
}
pub fn get(&self, key: &str) -> Option<&str> {
self.entries.get(key).map(String::as_str)
}
}
}
// --- Public API surface re-exported at the crate root ---
pub use self::error::{Error, Result};
pub use self::store::KvStore;
fn main() {
let mut db = KvStore::with_Quota(1);
db.insert("region", "us-east").unwrap();
println!("region = {:?}", db.get("region"));
println!("2nd insert error = {:?}", db.insert("env", "prod"));
}
region = Some("us-east")
2nd insert error = Err(QuotaExceeded { limit: 1 })