Crates

💭 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!");
    }
}

📖 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 --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

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

// # 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!");
    }
}

💭 //! 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, 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

📖 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, the cargo 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 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.

Last updated