Rust - Crates


Binary

A package can have any number of binary crates

src/main.rs is the default crate root for a binary crate with the same name as the package. To add multiple binary crates, place files in the src/bin/ directory. Each file will be a separate binary crate.

Library

A package can only have one (or none) library crates.

If a package directory contains src/lib.rs, the package contains a library crate with the same name as the package with src/lib.rs being the crate root. If both src/lib.rs and src/main.rs exist, then the package has two crates

Module

Modules are used to organize code within a crate into groups and control the privacy of items (public vs private). The C++ counterpart would be a namespace, except Rust takes it a step further.

Modules are created by using the mod keyword.

The modules in src/main.rs and src/lib.rs are called "crate roots" and is nested under crate.

Modules are private by default (like C++ private methods in a class). To make them public, you have to add the pub keyword. All parents of that module are now public, but children are still private unless specified otherwise.pub can be used on structs, enums, functions, and methods as well.

The keyword super allows you to refer to something in the parent's scope.

Example:

mod garden {
    // Nest a module inside of another
    pub mod food {
        // Add structs, enums, constants, traits, modules, functions, etc. here
        pub fn harvest() {
            super::soil::amount();
            super::super::plant_garden();
        }
        fn water() {}
    }

    pub mod soil {
        pub fn quality() {}
    
        pub fn amount() { quality(); }
    }
}

pub fn plant_garden() {
    // Relative path
    garden::soil::quality();
    // Absolute path
    crate::garden::soil::amount();
}

fn main() {
    // To access harvest, you'll have to do:
    crate::garden::food::harvest();
    // Both food and harvest must be marked as pub
}

Multiple Files

Multiple files example:

// src/garden.rs
pub mod food;

// src/garden/food.rs
pub fn harvest() {}

// src/main.rs
mod garden;

pub use crate::garden::food;

fn main() {
    food::harvest();
}

Package

A package is one or more crates that provide a some functionality. A package contains a Cargo.toml file which describes how to build the crates.

A package can contain as many binary crates as desired but can only contain a maximum of one library crate. A package must contain at least one crate (either library or binary).

Paths

To use a module tree, you'll need the path of the module. The path can be:

  • Absolute path: Starts at the crate root
  • Relative path: Starts from the current module. Uses self, super, or an identifier in the current module.

Which path method used in a module depends on how the module will be used. Absolute paths are generally recommended.

:: separate each identifier in a path (like C++ namespaces).

Public

The keyword pub makes something public, like a function, module, enum, struct etc.

However, structs are special. pub in front of them just makes them public, not the members inside. The members will not be read nor writable. To make them read/writable, add the pub keyword to the variable itself.

Example:

mod house {
    pub struct Kitchen {
        pub plates: i32,
        sinks: i8,
    }

    impl Kitchen {
        pub fn duel_sink(num_plates: i32) -> Kitchen {
            Kitchen {
                plates: num_plates,
                sinks: 2,
            }
        }
    }
}

pub fn build_home() {
    let mut home = house::Kitchen::duel_sink(8);
    home.plates += 1;

    // Cannot read nor write to private struct field
    //home.sinks += 1;
    //println!("Num sinks: {}", home.sinks);
}

Use

The keyword use can be used to import code and to shorten absolute/relative paths. It is recommended to import the module and not the specific function so when used, it is obvious that the call is not to a local function. When importing anything other than a function (structs, enums, etc), use the full path.

Example:

mod garden {
    // Nest a module inside of another
    pub mod food {
        // Add structs, enums, constants, traits, modules, functions, etc. here
        pub fn harvest() {}
        fn water() {}
    }

    pub mod soil {
        pub fn quality() {}
    
        pub fn amount() { quality(); }
    }
}

// Absolute path
use crate::garden::food;
// Relative path
use self::garden::soil;

// Full path for structs
use std::collections::HashMap;


fn main() {
    food::harvest();
    soil::quality();
	let mut map = HashMap::new();
}
As

When there is a name collision with use, the keyword as can be used in conjunction. Example:

use std::fmt::Result;
use std::io::Result as IoResult;

fn func1() -> Result { // ...  }
fn func2() -> IoResult<()> { // ... }
Pub Use

pub use can be used to re-export your code. For example, if you import A into B then import B into C, C would not have access to A because A would be private. If A was imported to B using pub use, then C would have access to A.

Re-exporting is especially useful when creating an API. Inside of src/lib.rs, you can do pub use self::path::to::thing to make others be able to use thing via use my_crate::thing in their code. This allows your API to be more strait-forward than your internal structure.

Nested Paths

Example:

use std::{cmp::Ordering, io}; // Bring in both std::cmp::Ordering and std::io
use std::io::{self, Write}; // std::io and std::io::Write
Glob

Brings in all public items from a path into scope. Example:

use std::collections::*;

Workspaces

A workspace is a set of packages that work together. To make a workspace, create a directory with a Cargo.toml file with a [workspace] section. You can then add packages to this workspace via cargo new and adding the name of the crate to the workspace section.