Step 6 of 7
Splitting a crate into files
So far every module was an inline mod name { ... } block in one file (this site compiles a single main.rs). In a real project, each module usually gets its own file. The module tree stays exactly the same; only where the code is stored changes.
First, two words people mix up:
- A crate is what the compiler builds in one go: a tree of modules starting at a crate root file. A binary crate has a
fn mainand becomes a program (root:src/main.rs). A library crate has nomain; it's code for other crates to use (root:src/lib.rs). - A package is what
cargo newcreates: aCargo.tomlplus one or more crates (at most one library, any number of binaries).
The garden from the first step, split into files:
garden-app/
├── Cargo.toml
└── src/
├── main.rs crate root: declares `mod garden;`
├── garden.rs the module garden
└── garden/
└── vegetables.rs the module garden::vegetables
src/main.rs:
mod garden; // "the module garden's code is in garden.rs"
fn main() {
garden::report();
}
src/garden.rs:
pub mod vegetables; // "its child vegetables is in garden/vegetables.rs"
pub fn report() {
println!("harvested {} carrots", vegetables::harvest());
}
src/garden/vegetables.rs:
pub fn harvest() -> u32 {
12
}
And the one-file version, which compiles to exactly the same program:
mod garden {
pub mod vegetables {
pub fn harvest() -> u32 {
12
}
}
pub fn report() {
println!("harvested {} carrots", vegetables::harvest());
}
}
fn main() {
garden::report();
}
harvested 12 carrots
The rules:
mod garden;with a semicolon instead of a block means "the body is in another file". The compiler looks forsrc/garden.rs, or the older stylesrc/garden/mod.rs.- Inside
garden.rs,pub mod vegetables;looks in a folder named after the parent:src/garden/vegetables.rs(orsrc/garden/vegetables/mod.rs). - A file does not become a module just by existing. Only a
modline in its parent includes it; a forgotten file is silently ignored (rust-analyzer shows "file not included in module tree"). modis not an import. Each module is declared once, in its parent. Everywhere else, refer to it with paths anduse.garden.rsorgarden/mod.rs? Both work. The newergarden.rsstyle (Rust 2018 and later) avoids an editor full of tabs namedmod.rs; some older projects still usemod.rs. Don't create both for the same module: that'serror[E0761].
Privacy, paths and use work exactly as in the inline version: inside vegetables.rs, super:: is garden, and crate:: is still the root.
A library and a binary in one package
A very common layout puts the logic in a library crate and keeps the binary thin. Tests, benchmarks and other programs can then use the library too.
inventory/
├── Cargo.toml [package] name = "inventory"
└── src/
├── lib.rs library crate root
├── stock.rs the library's module stock
└── main.rs binary crate root
src/lib.rs:
mod stock;
pub use stock::Item;
src/main.rs:
use inventory::Item; // the library crate, named after the package
fn main() {
let bolts = Item::new("bolt", 40);
println!("{}: {}", bolts.name, bolts.qty());
}
The binary is a separate crate that depends on the library: it refers to it by the package name (inventory::), and it can only use the library's pub items. Inside the library, paths start with crate:: as usual. (Dashes in a package name become underscores in code: package my-tool is my_tool::.)
More binaries go in src/bin/name.rs (run with cargo run --bin name), examples in examples/ (cargo run --example name), and integration tests in tests/ (the testing module).
Your task
Here is a project split into three files. Write the same program as one file, replacing each mod name; line with an inline mod name { ... } block that holds that file's code. It should print Ferris enters with 3 wins.
src/main.rs:
mod arena;
fn main() {
let fighter = arena::fighters::Fighter::new("Ferris", 3);
println!("{}", arena::announce(&fighter));
}
src/arena.rs:
pub mod fighters;
use fighters::Fighter;
pub fn announce(f: &Fighter) -> String {
format!("{} enters with {} wins", f.name, f.wins())
}
src/arena/fighters.rs:
pub struct Fighter {
pub name: String,
wins: u32,
}
impl Fighter {
pub fn new(name: &str, wins: u32) -> Fighter {
Fighter { name: name.to_string(), wins }
}
pub fn wins(&self) -> u32 {
self.wins
}
}
Challenge 2
Here's the library-plus-binary package from the lesson. Write it as one file: an inline mod inventory { ... } stands in for the library crate (src/lib.rs, with its private module stock inside), so that main.rs's line use inventory::Item; works unchanged. It should print bolt: 40.
src/lib.rs:
mod stock;
pub use stock::Item;
src/stock.rs:
pub struct Item {
pub name: String,
qty: u32,
}
impl Item {
pub fn new(name: &str, qty: u32) -> Item {
Item { name: name.to_string(), qty }
}
pub fn qty(&self) -> u32 {
self.qty
}
}
src/main.rs:
use inventory::Item;
fn main() {
let bolts = Item::new("bolt", 40);
println!("{}: {}", bolts.name, bolts.qty());
}
Challenge 3
Design it yourself. Write a one-file program whose inline modules match this file tree exactly:
src/
├── main.rs fn main
├── orders.rs pub struct Order { pub id: u32, pub cents: u32 }
│ pub fn sample() -> Vec<Order>
│ orders #1 (1000 cents) and #2 (250 cents)
├── orders/
│ └── tax.rs pub fn with_vat(cents: u32) -> u32
│ adds 20%: cents * 120 / 100
└── report.rs pub fn line(order: &Order) -> String
uses orders::tax::with_vat through a crate:: path
main prints the report line of every order from orders::sample():
order #1: 1200 cents incl. VAT
order #2: 300 cents incl. VAT
Previous: Re-exports with pub use Next: Cargo: dependencies, versions, features, workspaces