RustArena

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:

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:

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