cargo and crates.io

Customizing builds with release profiles

Cargo has two main profiles:

  • the dev profile
    • used when running cargo build
  • the release profile
    • used when running cargo build --release
# default settings
# `opt-level` setting controls the number of optimizations Rust
# will apply to the code, with a range of 0 to 3
[profile.dev]
opt-level = 0
 
[profile.release]
opt-level = 3

Full list of configuration options and defaults for each profile: https://doc.rust-lang.org/cargo/reference/profiles.html

Publishing a crate to crates.io

Documentation comments use three slashes /// instead of two and support markdown notation for formatting the text.

cargo doc --open will build the HTML of your current crate’s documentation.

Commonly used sections

  • panics: scenarios in which the function being documented could panic
  • errors: if the function returns a Result, describing the kinds of errors that might occur and what conditions might cause those errors to be returned can be helpful to callers so they can write code to handle the different kinds of errors in different ways
  • safety: if the function is unsafe to call, there should be a section explaining why the function is unsafe and covering the invariants that the function expects callers to uphold.

Documentation comments as tests

Adding example code blocks in your documentation comments can help demonstrate how to use your library. cargo test will run the code examples in your documentation as tests!

/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

Commenting contained items

Another styl of doc comment, //! adds documentation to the item that contains the comments rather than adding documentation to the items following the comments. Typically used inside the crate root file (src/lib.rs by convention) or inside a module to document the crate or the module as a whole.

//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
 
/// Adds one to the number given.
// --snip--
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

Documentation comments within items are useful for describing crates and modules especially. Use them to explain the overall purpose of the container to help your users understand the crate’s organization.

Exporting a convenient public API with pub use

//! # Art
//!
//! A library for modeling artistic concepts.
 
pub mod kinds {
    /// The primary colors according to the RYB color model.
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }
 
    /// The secondary colors according to the RYB color model.
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}
 
pub mod utils {
    use crate::kinds::*;
 
    /// Combines two primary colors in equal amounts to create
    /// a secondary color.
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        // --snip--
        unimplemented!();
    }
}
// a crate using the `art` crate's items with its internal
// strucutre exported => not good
use art::kinds::PrimaryColor;
use art::utils::mix;
 
fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}
// adding `pub use` statements to re-export items
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
 
pub mod kinds {
    // --snip--
}
// a program using the re-exported items from the `art` crate
// no internal structure exposed => better
use art::mix;
use art::PrimaryColor;
 
fn main() {
    let red = PrimaryColor::Red;
    let yellow = PrimaryColor::Yellow;
    mix(red, yellow);
}

Setting up a crates.io accounts

To create an account, visite https://crates.io and link to a Github account.

Then login with:

# api token stored locally in ~/.cargo/credentials
cargo login foobar

Adding metadata to a new crate

You can add metadata in the [package] section in Cargo.toml file.

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2018"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

Publishing in crates.io

cargo publish

When you’ve made changes to your crate and are ready to release a new version, you change the version value specified in your Cargo.toml file and republish. Use the Semantic Versioning rules to decide what an appropriate next version number is based on the kinds of changes you’ve made. Then run cargo publish to upload the new version.

# removing a versions
cargo yank --vers 1.0.1
# undo a yank
cargo yank --vers 1.0.1 --undo

Cargo workspaces

A workspace is a set of packages that share the same Cargo.lock and output directory.

$ mkdir add && cd add
$ cat << EOF > Cargo.toml
[workspace]
 
members = [
    "adder",
    "add-one",
]
EOF
$ cargo new adder
     Created binary (application) `adder` package
$ cargo new add-one --lib
     Created library `add-one` package
$ tree
├── Cargo.lock
├── Cargo.toml
├── add-one
   ├── Cargo.toml
   └── src
       └── lib.rs
├── adder
   ├── Cargo.toml
   └── src
       └── main.rs
└── target
$ echo 'add-one = { path = "../add-one" }' >> adder/Cargo.toml

Then we can use the add_one function from add-one crate in the adder crate:

// adder/src/main.rs
use add_one;
 
fn main() {
    let num = 10;
    println!(
        "Hello, world! {} plus one is {}!",
        num,
        add_one::add_one(num)
    );
}

Adding an external dependency

To add an external dependency on add-one, just add to the add-one/Cargo.toml:

rand = "0.8.3"

Building at root level will fetch the rand crate and the top-level Cargo.lock will contain information about the dependency of add-one on rand.

However, it won’t be usable on adder even though adder has dependency on add-one, i.e. the dependency is not transitive. In order to add rand to adder, you also need to add the dependency on adder/Cargo.toml.

Tests in workspaces

# at top level, it will run all crates tests and doc tests
cargo test
# run tests for a one particular crate in a worspace
cargo test -p add-one

Installing binaries from crate.io with cargo install

$ # command allows you to install and use binary crates locally
$ cargo install ripgrep
    Updating crates.io index
  Downloaded ripgrep v11.0.2
  Downloaded 1 crate (243.3 KB) in 0.88s
  Installing ripgrep v11.0.2
--snip--
   Compiling ripgrep v11.0.2
    Finished release [optimized + debuginfo] target(s) in 3m 10s
  Installing ~/.cargo/bin/rg
   Installed package `ripgrep v11.0.2` (executable `rg`)

Extending cargo with custom commands

Cargo is designed so you can extend it with new subcommands without having to modify Cargo. If a binary in your $PATH is named cargo-something, you can run it as if it was a Cargo subcommand by running cargo something. Custom commands like this are also listed when you run cargo —list. Being able to use cargo install to install extensions and then run them just like the built-in Cargo tools is a super convenient benefit of Cargo’s design!