Crates
Crates are the smallest amount of code the Rust compiler considers at a time.
When compiling a single .rs
file with rustc
, the compiler considers that file a crate of one of two possible types:
- Binary crate
- Library crate
Library Crate
The pub
keyword can be used to make a function accessible externally.
// Crates using this library can access the `add` function
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// Crates cannot access the `multiply` function
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
When compiling a library with rustc
, set the --crate-type
flag to lib
.
$ rustc --crate-type=lib adder.rs$ lslibadder.rlib
Binary Crate
Binary crates must have a main
function.
fn main() {
let sum = adder::add(5, 6);
println!("Sum: {sum}");
// Error! `private_function` is private
//let product = adder::multiply(5, 6);
}
Libraries used must be linked with the --extern
flag.
$ rustc prog.rs --extern adder=libadder.rlib$ ./progSum: 11
Package
A package is a bundle of one or more crates. We can use Cargo to simplifying building packages and their dependencies.
$ cargo new bin hello_world$ tree ./hello_world./hello_world/├── Cargo.toml└── src └── main.rs
Cargo.toml
is the manifest in the TOML format:
$ cat ./hello_world/Cargo.toml[package]name = "hello_world"version = "0.1.0"edition = "2021"[dependencies]
Library Package
A library package can be created with --lib
:
$ cargo new --lib hello_world_lib$ tree ./hello_world_lib./hello_world_lib/├── Cargo.toml└── src └── lib.rs
$ cat ./hello_world_lib/Cargo.toml[package]name = "hello_world_lib"version = "0.1.0"edition = "2021"[dependencies]
Binary + Library Package
Your package can generate both binary and library crates:
./hello_world├── Cargo.toml└── src ├── lib.rs └── main.rs
…Or even multiple binary crates:
./hello_world├── Cargo.toml└── src ├── lib.rs ├── main.rs └── bin ├── alt_bin.rs └── another_bin.rs
Building Packages
Packages can be built with cargo build
:
$ tree -L 3 ..├── Cargo.lock├── Cargo.toml├── src│ ├── lib.rs│ └── main.rs└── target ├── CACHEDIR.TAG └── debug ├── build ├── deps ├── examples ├── hello_world ├── hello_world.d ├── incremental ├── libhello_world.d └── libhello_world.rlib
Building Packages (2)
By default, packages are built with the dev
profile, with low optimizations, debug symbols and more:
$ cargo build Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Cargo places debug builds at ./target/debug/
.
Running cargo build
with the --release
flag compiles your package for production use and places the result at ./target/release/
.
$ cargo build --release Finished `release` profile [optimized] target(s) in 0.00s
Running packages
The cargo run
command will build your program and execute it:
fn main() {
println!("Hello, world!");
}
$ cargo run Compiling hello_world v0.1.0 (/path/to/hello_world) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s Running `target/debug/hello_world`Hello, world!
The release equivalent is cargo run --release
.
Modules
Modules are logical units of related code. They provide allow controlling the namespace and visibility of name.
mod adder {
fn add(x: i32, y: i32) -> i32 {
x + y
}
}
mod sub {
fn sub(x: i32, y: i32) -> i32 {
x - y
}
}
// Here, the visible names are `adder` and `sub`
fn main() {
println!("Hello, world!");
}
Visibility
Visibility is a property that determines whether a module can refer to the name of an item.
By default, items in modules have private visibility, but can be made public with the pub
keyword.
If an item is visible, it can be accessed through a path, which are components of the module hierarchy separated by ::
double colons.
mod adder {
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
}
fn main() {
let x = adder::add(5, 6);
println!("Sum: {x}");
}
Nested modules
Modules can be nested.
The use
keyword allows making the name of an item directly accessible in the current namespace by specifying its path:
mod arithmetic {
pub mod adder {
pub fn add(x: i32, y: i32) -> i32 { x + y }
}
pub mod multiplier {
pub fn mul(x: i32, y: i32) -> i32 { x * y }
}
}
use arithmetic::adder::add;
fn main() {
let x = add(5, 6);
println!("Sum: {x}");
}
Relative Path
In a module, self
refers to the current module, and super
refers to its parent module.
mod arithmetic {
pub mod adder {
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
}
pub fn add_one(x: i32) -> i32 {
self::adder::add(x, 1)
}
pub mod subber {
pub fn sub_one(x: i32) -> i32 {
super::adder::add(x, -1)
}
}
}
Absolute Paths
Absolute paths start with crate
, which refers to the crate root. This is usually src/lib.rs
for a library crate and src/main.rs
for a binary crate.
mod arithmetic {
pub mod adder {
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
}
pub fn add_one(x: i32) -> i32 {
crate::arithmetic::adder::add(x, 1)
}
pub mod subber {
pub fn sub_one(x: i32) -> i32 {
crate::arithmetic::adder::add(x, -1)
}
}
}
use
Declarations
use
declarations can be bind multiple names, rename items or made public.
mod arithmetic {
pub use self::{adder::add, multiplier::multiply};
mod adder {
pub fn add(x: i32, y: i32) -> i32 { x + y }
}
mod multiplier {
pub fn multiply(x: i32, y: i32) -> i32 { x * y }
}
}
use arithmetic::multiply as mul;
fn main() {
let x = mul(5, 6);
println!("Product: {x}");
}
Modules in Separate Files
Modules can be in placed in separate files:
Current File | Module Declaration | Possible modules |
---|---|---|
src/lib.rs |
mod arithmetic; |
src/arithmetic.rs src/arithmetic/mod.rs |
src/arithmetic.rs src/arithmetic/mod.rs |
mod adder; |
src/arithmetic/adder.rs src/arithmetic/adder/mod.rs |
.├── arithmetic│ └── adder.rs├── arithmetic.rs└── lib.rs
.├── arithmetic│ ├── adder.rs│ └── mod.rs└── lib.rs
.├── arithmetic│ ├── adder│ │ └── mod.rs│ └── mod.rs└── lib.rs
Binary + Library Package
For packages with both a library and binary crate, the binary crate can access the library crate through the name of your package:
pub fn adder(x: i32, y: i32) -> i32{
x + y
}
fn main() {
let sum = hello_world::adder(1, 2);
println!("{sum}");
}
[package]name = "hello_world"version = "0.1.0"edition = "2021"[dependencies]
Package Dependencies
Package dependencies are specified in the manifest Cargo.toml
:
[package]name = "djot"version = "0.1.0"edition = "2021"[dependencies]regex = "1.10.6"quick-js = { version = "0.4.1", features = ["patched"] }tree-sitter-toml-ng = { path = "../../vendor/tree-sitter-toml" }minijinja = { git = "https://github.com/mitsuhiko/minijinja.git", rev = "1f119ec" }
Cargo will take care of compiling and linking your program, and you get access to the library crates of the listed dependencies in your package.