Crates and cargo
cargo
is a fundamental element of this description being the main toolchain manager. It is more a roundup I needed to reconcile cargo
file layout and default behavior with rust
You can find other references about modules, crates and packages in the 7th chapter of the Rust Book.
The code in this post is available on GitHub with MIT license.
a few things about cargo
basic default file layout for cargo
cargo has a default layout: with the same toml you may expect
while in the binary case you can also run the code with
- a binary project as generated by
projectdir/ ├─ Cargo.toml └─ src/ └─ main.rs
- a library project
projectdir/ ├─ Cargo.toml └─ src/ └─ lib.rsin both case you get compilation with
cargo build
cargo run
compilation target basics
cargo
has some commands and options aimed to compile differently the code and possibly also choose specific output
When compiling the code with commands build
or run
the default target is dev
while production compilation requires the flag cargo build --release
: this triggers also some optimizations.
library and tests
library example generated by
cargo new projectname --lib
contains some test example to start unit testing: looks like this is the suggested feature
1: #[cfg(test)] 2: mod tests { 3: #[test] 4: fn it_works() { 5: assert_eq!(2 + 2, 4); 6: } 7: }
- line 1 contains a macro that ensure the following entity to be compiled only if the "test" target is selected, which is what happens running
cargo test
- line 2 starts a module within this file, this will be compiled only according to the macro at line 1
- line 3 contains a macro which will add some boilerplate code used to execute the following function and collect its result when executing tests
- line 5 contains a macro checking the two arguments for equality and then raising an exception if the do not equals
- equality is defined by a specific trait
- if the exception is raised the test is failed
cargo test
- this command also executes all the tests
how rust groups code
the code hierarchy
Rust uses this hierarchical structure
packages └─ crates └─ modules ├─ functions ├─ structures and enums └─ traitspackages are the highest level: they can be external, built-in (e.g.
std
) or user created. Each project defined by a Cargo.toml is a package
Each package can contain a hierarchy of crates, which means they have the same root and do not have circular dependencies
Each crate can contain a hierarchy of modules with possibly different access permissions
actual code is always in the modules
the crate and modules default
when creating a library project with cargo the
accessing modules from within the same file can be done via a path
lib.rs
file contains the code which will be attributed by default to the crate with the same name as the project
all modules are defined by using the mod
keyword followed by a block; modules definition can be nested
Modules act as namespace and as incapsulation protection. While modules on the same file are accessible, their inner objects should be marked as pub
(short for public) to be accessible
mod mymodule { pub fn myfunc(){} pub mod mysubmodule{ pub fn myotherfunc(){} } }
fn main(){ mymodule::myfunc(); mymodule::mysubmodule::myotherfunc(); }
use keyword and navigating the crate hierarchy
The rust book has a nice section dedicated to this subject. The main takeaways are:
- the
use
keyword let you access other modules in the hierarchy: levels are separated by a double colon ::use std::io;
- a module in the current project can refer to its ancestors using the
super
keyword at the beginning of the sequenceuse super::another::branch;
- the root of all modules in this project is referred with the keyword crate
use crate::sublevel;
- one or more elements can be exposed out of their modules; possible clashes can be handled using aliases
use std::io::Result; use std::io::{self, Write}; use std::fmt::Result as FmtResult;
mixing all together
other compilation targets
files in the
will compile the file
the test command will build and execute also the tests under the tests directory.
But now things starts to be tricky: how can you import the modules in the src directory in order to test them?
the same applies for examples
examples
and tests
are compiled according to the appropriate target
projectdir/ ├─ Cargo.toml ├─ src/ │ └─ lib.rs ├─ examples/ └─ tests/e.g. the following command
cargo build --example mycode
examples/mycode.rs
; to run it
cargo run --example mycode
accessing the default crate from test files
suppose we have the following function in our library
the crate name is taken from the Cargo.toml name attribute
pub fn myfunc(){}
use rust_blog; #[test] fn test_access_base(){ rust_blog::myfunc(); }
accessing modules from different files
the following syntax, added to the lib.rs file is doing two actions:
- the mod command search for a file (in this case in the current directory)
- the pub modifier re-exports the command
pub mod poly;
- this allows to add a test file like the following: note that this looks for a
src/lib.rs
which in turn redirects tosrc/poly.rs
file
use rust_blog::poly; mod test { #[test] fn it_works() { assert_eq!(2 + 2, 4); } }