π Crates are 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 --bin greetings
touch greetings/src/lib.rs
// # It generates,
.
βββ 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 runing tests
mod tests { // seperates tests from code
use super::hello; // import root hello function
#[test]
fn test_hello() {
assert_eq!(hello(), "Hello, world!");
}
}
lib.rs can link with multiple files.
// # Think we run
cargo new --bin phrases
touch phrases/src/lib.rs
touch phrases/src/greetings.rs
// # It generates,
.
βββ 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 --bin phrases
cargo new phrases/greetings
// # It generates,
.
βββ 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
// # Think we run
cargo new test_crate_hello_world
// # It generates,
.
βββ 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!");
}
}
π‘ 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, 1. We have to create an account on crates.io to acquire an API token 2. Then run cargo login <token> with that API token and cargo publish
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, the cargo publish sub-command will automatically package up the local crate if a copy isnβt found already.
π― 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 an another crate.
// # Think we run
cargo new --bin greetings
// # It generates,
.
βββ 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.
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;
The other way is uploading it to and use it as a dependency when needed.
π§ First, letβs create a simple βHello worldβ crate and upload it to .
π //! 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 , cargo generates the documentation from these doc comments and host it on .
This is with more details.
Youβll need an account on crates.io to acquire an API token. To do so, and log in via a GitHub account (required for now). After this, visit your page and run the cargo login command specified.
The name of our crate is test_crate_hello_world. So it can be found on, π¦ π