A Rust project is made of:
A project description (i.e.:
Cargo.tomlfile).Code files.
Declarations of code elements (i.e.: functions, structures, enumerate types, traits, ...) written inside the code files.
It is organized as a hierarchy of the following elements:
Package: A Cargo project containing one or more crates.
Crate: What are defined and distributed by packages and downloadable from crates.io.
Module: A group of one or more code files.
Path: The exact identification of a code element inside a file.
The following chapters present in more details those definitions.
26.1.1Package¶
When we start a new project/package with the cargo command-line
tool, we have only the choice between two options:
--bin(the default) to create a binary crate project.--libto create a library crate project.
26.1.1.1Binary package¶
With the following command, we create a new project named foo,
containing a single binary crate:
cargo new --bin foo
cd foo Creating binary (application) `foo` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
A Rust package is a folder with at its root:
A
Cargo.tomlfile.A
srcfolder, containing the Rust code.Eventually a
Cargo.lockfile, if we have added dependencies.Eventually a
testsfolder.
Our foo package contains only a Cargo.toml file and one source
file src/main.rs:
eza -T ..
├── Cargo.toml
└── src
└── main.rs
A package is defined by its Cargo.toml file. It contains (among
other things):
The project name.
The dependencies.
At most one library crate.
Zero or more binary crates (i.e.: binary executables).
Here is the content of your Cargo.toml file:
bat Cargo.toml[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
The content of the main.rs is simply a single main() function:
bat src/main.rsfn main() {
println!("Hello, world!");
}
The default generated project compiles and runs:
cargo run Compiling foo v0.1.0 (/tmp/tmp.5gStzYSnxv/foo)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/foo`
Hello, world!
26.1.1.2Library package¶
With the --lib argument, we define a library crate:
cargo new --lib foo2
cd foo2 Creating library `foo2` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
We get a lib.rs inside the src folder:
eza -T ..
├── Cargo.toml
└── src
└── lib.rs
The content of the Cargo.toml file still conveys no information on
the crate type:
bat Cargo.toml[package]
name = "foo2"
version = "0.1.0"
edition = "2024"
[dependencies]
The lib.rs file contains a public function example and its test
function:
bat src/lib.rspub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
The default generated project compiles and passes its test:
cargo test Compiling foo2 v0.1.0 (/tmp/tmp.5gStzYSnxv/foo/foo2)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
Running unittests src/lib.rs (target/debug/deps/foo2-0f55e11fb220cf29)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests foo2
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
26.1.2Crate¶
By default a package contains only one crate, described inside the
Cargo.toml file and whose code is stored inside the src folder.
Multiple crates are possible in a project, see
Multiple crates.
A crate contains zero or more modules defined hierarchically inside
the src folder.
26.1.3Module¶
Modules are namespaces that regroup related code. They are part of a crate, and can be declared:
Internally: they are defined inside a file containing other code.
In a single file: the whole module is part of a single file.
In a folder: the module is divided among multiple files that all belong to the same folder. See Folder module.
26.1.3.1Internal module¶
An internal module, is a module defined using the mod keyword. Here
is the module math in which we define a single function add()
(the following examples are pure Rust snippets, not executed under
the bash kernel of this page):
mod math {
pub fn add(a: i16, b: i16) -> i16 {
a + b
}
}To call the add() function of the math module, we need to specify
its path:
math::add(2, 6)26.1.3.2File module¶
A module can be defined using a file, in which case the whole file is dedicated to the module’s content and the file name is the module’s name.
Let us create a new binary project to illustrate that:
cargo new --bin foo4
cd foo4 Creating binary (application) `foo4` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
We create the code file math.rs that we will use as a module. We
define inside a function add():
bat src/math.rspub fn add(a: i16, b: i16) -> i16 {
a + b
}
Now we change the main.rs file so that it contains a main()
function that calls the math::add() function. Note the declaration
of the module math using the mod keyword. This declaration tells
the compiler to look for a file math.rs inside the same folder as
the main.rs file and to load it as a module named math:
bat src/main.rsmod math;
fn main () {
println!("{}", math::add(2, 6));
}
The program compiles & runs:
cargo run Compiling foo4 v0.1.0 (/tmp/tmp.5gStzYSnxv/foo/foo2/foo4)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/foo4`
8
26.1.4Path¶
The exact location of an item into a crate is called a path.
If we need to access an item inside an external crate, we use an
absolute path. Cargo will find the location of the needed crate by
looking into its installation folders. In the following example, to
access the from_millis() function inside the std crate, we use the
full path of the item:
let t = std::time::Duration::from_millis(700);
tWhen referring a crate’s item from inside a crate we may use relative or absolute path.
For instance, in the following math module defined inside the
current file, the double() function can access the add() function
from the arithmetic sub-module relatively. We can use a relative
path to access it:
mod math {
pub mod arithmetic {
pub fn add(a: i16, b: i16) -> i16 {
a + b
}
}
pub fn double(n: i16) -> i16 {
arithmetic::add(n, n)
}
}
math::double(5)26.1.5Use keyword¶
The use keyword allows to avoid repeating a full absolute
localisation of a module or an item’s module. It is especially useful
with modules defined inside a deep hierarchy.
For instance, the std::time::Duration::from_millis() function is a
bit long name. use can help by letting us access directly the
Duration structure:
use std::time::Duration;
let t = Duration::from_millis(300);
tWe may also rename the Duration structure:
use std::time::Duration as Dur;
let t = Dur::from_millis(300);
tIt is also possible to access directly the whole public content of a
module using the * wildcard, like in the following example:
use std::collections::*;
let lst = LinkedList::from([1, 2, 3]);
lstHowever, a better practice is to name explicitly each item we need to access, possibly using curly braces to group multiple items:
use std::collections::{LinkedList, VecDeque};
let lst = LinkedList::from([1, 2, 3]);
let deq = VecDeque::from([-1, 0, 1]);
(lst, deq)