Day 1 - Cargo subcommands

Welcome to the second edition of 24 Days of Rust!

Two years ago in December 2014 the first edition was pretty well received by the Rust community. That was a hard time - we were still before the 1.0 release of the language. I had to keep up with updating my examples day by day since almost every nightly broke something. Even so, I managed to publish all 24 blogposts on schedule :-)

This year I'm back with another series of articles about Rust tools and libraries. The languge is at version 1.13 as I'm writing this introduction. It's definitely more stable and mature at this point than it was in those savage pre-1.0 times. Some of my examples will probably still require a nightly release, but I'll try to keep them updated when relevant features get into stable.

With that, let's dive in into our first topic, which is....

Cargo subcommands

By now you're probably familiar with commands like cargo build, cargo run, cargo test etc. But these are just the built-in commands. Cargo is extensible, allowing you to plug in third party subcommands. I'll briefly describe some of those in this post. Read on!

Subcommand discovery

First of all, what are those subcommands and how does Cargo know what code to run? Turns out it's very simple. If you have an executable named cargo-embiggen in your PATH, then it will be available as cargo embiggen subcommand.

cargo fmt

If you write code in other languages, there are probably some coding style guides you follow (or at least have heard of). In Python land there's PEP 8, PHP has their coding standards spread across several PSRs, and a lot of organizations have their internal style guides for the languages they use. But having a coding style is one thing, actually sticking to it is another. Wouldn't it be nice to have some automated tool to enforce code formatting? Fortunately there's one for Rust.

Enter rustfmt, which is the officially supported code formatter for Rust. If you install it with cargo install rustfmt, then a cargo fmt command becomes available. Run it on your project to fix any code that doesn't necessarily follow best formatting practices. For example, rustfmt can turn this:

fn make_it_happen<T, U>(x: &Option<T>, y: U) -> Result<U, String> where T: std::io::Write, U: std::io::Read
{
    match *x
    {
        Some(_) => {Ok(y)},
        None => Err("not happening".into())
    }
}

into this:

fn make_it_happen<T, U>(x: &Option<T>, y: U) -> Result<U, String>
    where T: std::io::Write,
          U: std::io::Read
{
    match *x {
        Some(_) => Ok(y),
        None => Err("not happening".into()),
    }
}

rustfmt fixed where clause, unnecessary braces and trailing commas. The code is now consistent with current Rust style guidelines. If you use one of popular text editors or IDEs, you can probably hook rustfmt/cargo fmt on save, so all your Rust sources will be formatted automatically.

cargo outdated

This command is the equivalent of npm outdated or pip list --outdated. Install it with cargo install cargo-outdated, and then run cargo outdated in your project directory. For example in one of my projects:

$ cargo outdated
Checking for SemVer compatible updates...Done
Checking for the latest updates...Done
The following dependencies have newer versions available:

    Name                   Project Ver  SemVer Compat  Latest Ver
    hound                     1.1.0          --          2.0.0
    rustfft                   1.0.0        1.0.1         1.0.1

As you can see, it tells me I can probably safely update the rustfft crate to its latest version. On the other hand, hound might have introduced breaking changes, so there's no SemVer-compatible version.

Note: always test your project after upgrading your dependencies, even if it's only a patch version. SemVer is only a promise not to break your code, not a strict guarantee.

cargo clippy

clippy (which takes its name from an obnoxious MS Office feature) is actually a very useful tool, compared to its namesake. It is a linter that checks your code against common mistakes and suggests idiomatic improvements. At this moment clippy has over 170 different checks, so it's definitely worth trying out on your codebase.

If you install it with cargo install clippy (requires nightly), you can run it as a Cargo subcommand - cargo clippy.

For example:

fn needs_improving(x: bool) {
    if x.clone() == true {
        println!("x is true");
    } else {
        if 42 > std::i32::MAX {
            panic!("WAT");
        }
    }
}

This code is well-formatted and compiles succesfully. However, there are a few things that could be improved here and clippy tells you just that:

warning: this `else { if .. }` block can be collapsed, #[warn(collapsible_if)] on by default
  --> src\example.rs:55:12
   |
55 |     } else {
   |            ^
   |
help: try
   |     } else if 42 > std::i32::MAX {
   |     panic!("WAT");
   | }
   = help: for further information visit https://github.com/Manishearth/rust-clippy/wiki#collapsible_if

warning: equality checks against true are unnecessary, #[warn(bool_comparison)] on by default
  --> src\example.rs:53:8
   |
53 |     if x.clone() == true {
   |        ^^^^^^^^^^^^^^^^^
   |
help: try simplifying it as shown:
   |     if x.clone() {
   = help: for further information visit https://github.com/Manishearth/rust-clippy/wiki#bool_comparison

warning: using `clone` on a `Copy` type, #[warn(clone_on_copy)] on by default
  --> src\example.rs:53:8
   |
53 |     if x.clone() == true {
   |        ^^^^^^^^^
   |
help: try removing the `clone` call
   |     if x == true {
   = help: for further information visit https://github.com/Manishearth/rust-clippy/wiki#clone_on_copy

warning: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false, #[warn(absurd_extreme_comparisons)] on by default
  --> src\example.rs:56:12
   |
56 |         if 42 > std::i32::MAX {
   |            ^^^^^^^^^^^^^^^^^^
   |
   = help: because std::i32::MAX is the maximum value for this type, this comparison is always false
   = help: for further information visit https://github.com/Manishearth/rust-clippy/wiki#absurd_extreme_comparisons

Note how each of these checks points you to clippy wiki for more explanation if it's still unclear from the message. Thanks clippy!

Before I finish this article, I need to point out that there's unfortunately no (un)official tutorial on how to write a custom Cargo subcommand. That's an idea for a future blogpost :-)

Further reading