Managing growing projects with packages, crate and modules

These features, sometimes collectively referred to as the module system, include:

  • Packages: A Cargo feature that lets you build, test, and share crates
  • Crates: A tree of modules that produces a library or executable
  • Modules and use: Let you control the organization, scope, and privacy of paths
  • Paths: A way of naming an item, such as a struct, function, or module

Packages and crates

$ cargo new my-project
     Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs

When we entered the command, Cargo created a Cargo.toml file, giving us a package. Looking at the contents of Cargo.toml, there’s no mention of src/main.rs because Cargo follows a convention that src/main.rs is the crate root of a binary crate with the same name as the package. Likewise, Cargo knows that if the package directory contains src/lib.rs, the package contains a library crate with the same name as the package, and src/lib.rs is its crate root.

Modules to control scope and privacy

// src/lib.rs
mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
 
        fn seat_at_table() {}
    }
 
    mod serving {
        fn take_order() {}
 
        fn serve_order() {}
 
        fn take_payment() {}
    }
}
 
// module tree for the above structure:
// crate
//  └── front_of_house
//      ├── hosting
//      │   ├── add_to_waitlist
//      │   └── seat_at_table
//      └── serving
//          ├── take_order
//          ├── serve_order
//          └── take_payment

Paths for referering to an item in the module tree

A path can take two forms:

  • An absolute path starts from a crate root by using a crate name or a literal crate.
  • A relative path starts from the current module and uses self, super, or an identifier in the current module.
mod front_of_house {
    // exposing paths with the `pub` keyword:
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}
 
pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();
 
    // Relative path
    front_of_house::hosting::add_to_waitlist();
}
 
// calling a function using a relative path starting with super
fn serve_order() {}
 
mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order();
    }
 
    fn cook_order() {}
}
 
// making structs and enums public
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }
 
    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}
 
pub fn eat_at_restaurant() {
    // Order a breakfast in the summer with Rye toast
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // Change our mind about what bread we'd like
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);
 
    // The next line won't compile if we uncomment it; we're not allowed
    // to see or modify the seasonal fruit that comes with the meal
    // meal.seasonal_fruit = String::from("blueberries");
}
// for public enum, all of its variantes are then public
mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}
 
pub fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

Bringing paths into scope with the use keyword

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}
 
// idiomatic `use` paths
use crate::front_of_house::hosting;
// or in relative path
// use self::front_of_house::hosting;
 
pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}
// providing new names with the `as` keyword
use std::fmt::Result;
use std::io::Result as IoResult;
 
fn function1() -> Result {
    // --snip--
    Ok(())
}
 
fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}
// re-exporting names with `pub use`
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}
 
// By using pub use, external code can now call the add_to_waitlist function
// using hosting::add_to_waitlist. If we hadn’t specified pub use, the
// eat_at_restaurant function could call hosting::add_to_waitlist in its scope,
// but external code couldn’t take advantage of this new path.
pub use crate::front_of_house::hosting;
 
pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}
// If we want to bring all public items defined in a path into scope, we can
// specify that path followed by *, the glob operator:
use std::collections::*;

Using external packages

Adding rand as a dependency in Cargo.toml tells Cargo to download the rand package and any dependencies from crates.io and make rand available to our project.

Seperating modules into different files

// src/lib.rs
mod front_of_house;
 
pub use crate::front_of_house::hosting;
 
pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}
// src/front_of_house.rs
pub mod hosting {
    pub fn add_to_waitlist() {}
}
// src/front_of_house.rs
// use a semicolon rathat than a block tells Rust to load the contents of the module
// from another file with the same name as the module
pub mod hosting;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
}