Working with multiple files

Crates and packages

You can think of crate as the code that is compiled and package as the Rust project, which also includes the dependencies.

Working with multiple files

An example

Cargo is actually a package that contains the binary crate for the command-line tool you’ve been using to build your code.

The Cargo package also contains a library crate that the binary crate depends on. Other projects can depend on the Cargo library crate to use the same logic the Cargo command-line tool uses.

Working with multiple files

Crate roots

A crate root is the first file that the compiler looks for, all the other files are compiled because the crate root imports them.

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.

Working with multiple files

Modules

Modules are a group of files that define some functions, struct, enums, etc. You may know them as namespaces. Modules are useful to organize your code.

Working with multiple files

Declaring modules

In the crate root file, you can declare new modules; say, you declare a garden module with mod garden;. The compiler will look for the module’s code in these places:

Working with multiple files

Module paths

Once a module is part of your crate, you can refer to code in that module from anywhere else in that same crate (as long as the privacy rules permit it) using the path to the module. For example, an Asparagus type in the garden vegetables module would be found at crate::garden::vegetables::Asparagus

Working with multiple files

Public vs Private

Code within a module is private from its parent modules by default. To make a module public, declare it with pub mod instead of mod. To make items within a public module public as well, use pub before their declarations.

Working with multiple files

The use keyword

Within a scope, the use keyword creates shortcuts to items to reduce repetition of long paths. In any scope that can refer to crate::garden::vegetables::Asparagus, you can create a shortcut with use crate::garden::vegetables::Asparagus; and from then on you only need to write Asparagus to make use of that type in the scope.

Working with multiple files

Example project

Suppose we have a project like this:


backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs
    
Working with multiple files

Example project

In src/main.rs


use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {:?}!", plant);
}
    
Working with multiple files

Example project

In src/garden.rs


pub mod vegetables;
    

In src/garden/vegetables.rs


#[derive(Debug)]
pub struct Asparagus {}
    
Working with multiple files

Relative paths

We have three keyword for defining module paths:

Working with multiple files

More about use

You can alias names with as, useful in case of name collision.


use std::io::Result as IoResult;
    

More about use

You can group nested paths.


use std::cmp::Ordering;
use std::io;
    

use std::{cmp::Ordering, io};
    
Working with multiple files

More about use

You can import everything from a module with '*'.


use std::collections::*;
    

A common Rust pattern for libraries is to have all the most common stuff to import in the prelude module.


use bevy::prelude::*;