💭 Crates are a bit similar to the packages in some other languages. Crates compile individually. If the crate has child file modules, those files will get merged with the crate file and compile as a single unit.
💭 A crate can produce an executable/ a binary or a library. src/main.rs
is the crate root/ entry point for a binary crate and src/lib.rs
is the entry point for a library crate.
01. lib.rs on executable crate
💡 When writing binary crates, we can move the main functionalities to src/lib.rs
and use it as a library from src/main.rs
. This pattern is quite common on executable crates.
// # Think we run,
cargo new greetings
touch greetings/src/lib.rs
// # It generates,
greetings
├── Cargo.toml
└── src
├── lib.rs
└── main.rs
// # Think we modify following files,
// 01. greetings/src/lib.rs
pub fn hello() {
println!("Hello, world!");
}
// 02. greetings/src/main.rs
extern crate greetings;
fn main() {
greetings::hello();
}
💯 As I mentioned earlier, in here we use simplest examples to reduce the complexity of learning materials. But this is how we need to write
greetings/src/lib.rs
to make the code more testable.
// greetings/src/lib.rs
pub fn hello() -> String {
//! This returns `Hello, world!` String
("Hello, world!").to_string()
}
// 01. Tests for `hello()`
#[test] // Indicates that this is a test function
fn test_hello() {
assert_eq!(hello(), "Hello, world!");
}
// 02. Tests for `hello()`, Idiomatic way
#[cfg(test)] // Only compiles when running tests
mod tests { // Separates tests from code
use super::hello; // Import root `hello()` function
#[test]
fn test_hello() {
assert_eq!(hello(), "Hello, world!");
}
}
📖 When importing a crate that has dashes in its name “like-this”, which is not a valid Rust identifier, it will be converted by changing the dashes to underscores, so you would write
extern crate like_this;
lib.rs
can link with multiple files.
// # Think we run,
cargo new phrases
touch phrases/src/lib.rs
touch phrases/src/greetings.rs
// # It generates,
phrases
├── Cargo.toml
└── src
├── greetings.rs
├── lib.rs
└── main.rs
// # Think we modify following files,
// 01. phrases/src/greetings.rs
pub fn hello() {
println!("Hello, world!");
}
// 02. phrases/src/main.rs
extern crate phrases;
fn main() {
phrases::greetings::hello();
}
// 03. phrases/src/lib.rs
pub mod greetings; // ⭐️ Import `greetings` module as a public module
02. Dependency crate on Cargo.toml
When the code in the lib.rs
file is getting larger, we can move those into a separate library crate and use it as a dependency of the main crate. As we mentioned earlier, a dependency can be specified from a folder path, git repository or by crates.io.
a. Using folder path
Let’s see how to create a nested crate and use it as a dependency using folder path,
// # Think we run,
cargo new phrases
cargo new phrases/greetings --lib
// # It generates,
phrases
├── Cargo.toml
├── greetings
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── src
└── main.rs
// # Think we modify following files,
// 01. phrases/Cargo.toml
[package]
name = "phrases"
version = "0.1.0"
authors = ["Dumindu Madunuwan"]
[dependencies]
greetings = { path = "greetings" }
// 02. phrases/greetings/src/lib.rs
pub fn hello() {
println!("Hello, world!");
}
// 03. phrases/src/main.rs
extern crate greetings;
fn main() {
greetings::hello();
}
b. Using git repository
If you want to use a library crate on multiple projects, one way is moving crate code to a git repository and use it as a dependency when needed.
// -- Cargo.toml --
[dependencies]
// 01. Get the latest commit on the master branch
rocket = { git = "https://github.com/SergioBenitez/Rocket" }
// 02. Get the latest commit of a specific branch
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "v0.3" }
// 03. Get a specific tag
rocket = { git = "https://github.com/SergioBenitez/Rocket", tag = "v0.3.2" }
// 04. Get a specific revision (on master or any branch, according to rev)
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "8183f636305cef4adaa9525506c33cbea72d1745" }
c. Using crates.io
The other way is uploading it to crates.io and use it as a dependency when needed.
🚧 First, let’s create
a simple “Hello world” crate and upload
it to crates.io.
// # Think we run,
cargo new test_crate_hello_world --lib
// # It generates,
test_crate_hello_world
├── Cargo.toml
└── src
└── lib.rs
// # Think we modify following files,
// 01. test_crate_hello_world/Cargo.toml
[package]
name = "test_crate_hello_world"
version = "0.1.0"
authors = ["Dumindu Madunuwan"]
description = "A Simple Hello World Crate"
repository = "https://github.com/dumindu/test_crate_hello_world"
keywords = ["hello", "world"]
license = "Apache-2.0"
[dependencies]
// 02. test_crate_hello_world/src/lib.rs
//! A Simple Hello World Crate
/// This function returns the greeting; `Hello, world!`
pub fn hello() -> String {
("Hello, world!").to_string()
}
#[cfg(test)]
mod tests {
use super::hello;
#[test]
fn test_hello() {
assert_eq!(hello(), "Hello, world!");
}
}
💭 //! doc comments are used to write crate and module-level documentation. On other places, we have to use /// outside of the block. And when uploading a crate to crates.io, cargo generates the documentation from these doc comments and host it on docs.rs.
💡 We have to add the description and license fields to Cargo.toml
. Otherwise, we will get error: api errors: missing or empty metadata fields: description, license. Please see http://doc.crates.io/manifest.html
To upload this to crates.io,
- We have to create an account on crates.io to acquire an API token
- Then run
cargo login <token>
with that API token and cargo publish
📖 This is how it describes on Cargo Docs with more details.
- You’ll need an account on crates.io to acquire an API token. To do so, visit the home page and log in via a GitHub account (required for now). After this, visit your Account Settings page and run the
cargo login
command specified. Ex.cargo login abcdefghijklmnopqrstuvwxyz012345
- The next step is to package up your crate into a format that can be uploaded to crates.io. For this we’ll use the
cargo package
sub-command. - Now, it can be uploaded to crates.io with the
cargo publish
command. - If you’d like to skip the
cargo package
step, thecargo publish
sub-command will automatically package up the local crate if a copy isn’t found already.
The name of our crate is test_crate_hello_world
. So it can be found on,
📦 https://crates.io/crates/test_crate_hello_world
📑 https://docs.rs/test_crate_hello_world
💯 crates.io supports readme files as well. To enable it, we have to add the readme field to Cargo.toml. Ex:
readme="README.md"
🏗️ Okay then, Let’s see how we can use this from another crate.
// # Think we run,
cargo new greetings
// # It generates,
greetings
├── Cargo.toml
└── src
└── main.rs
// # Think we modify following files,
// 01. greetings/Cargo.toml
[package]
name = "greetings"
version = "0.1.0"
authors = ["Dumindu Madunuwan"]
[dependencies]
test_crate_hello_world = "0.1.0"
// 02. greetings/src/main.rs
extern crate test_crate_hello_world;
fn main() {
println!("{}", test_crate_hello_world::hello());
}
By default, Cargo looks dependencies on crates.io. So we have to add only the crate name and a version string to Cargo.toml
and then run cargo build
to fetch the dependencies and compile them.