Log in to access Rust Adventure videos!

Lesson Details

Clap, with the derive feature, allows us to specify the arguments we want to accept in our CLI by defining regular Rust structs and using derive macros.

use clap::Parser;

#[derive(Parser, Debug)]
struct Args {
    #[clap(short, long, default_value = "post")]
    layout: String,

    #[clap(short, long = "tag")]
    tags: Vec<String>,

    #[clap(short = 'T', long, default_value = "A Post")]
    title: String,

    #[clap(short, long, default_value = "draft")]
    status: String,

    #[clap(short, long, default_value = "content")]
    output_dir: String,
}

fn main() {
    let args = Args::parse();
    dbg!(args);
}

This completely defines our CLI’s interface. Running the CLI right now provides us with the Args struct, which is an arbitrary name, full of default values.

We know this because the dbg! macro shows us the args value when we run the program.

❯ cargo run
   Compiling bitflags v2.3.3
   Compiling utf8parse v0.2.1
   Compiling anstyle v1.0.1
   Compiling anstyle-query v1.0.0
   Compiling colorchoice v1.0.0
   Compiling libc v0.2.147
   Compiling strsim v0.10.0
   Compiling clap_lex v0.5.0
   Compiling once_cell v1.18.0
   Compiling anstyle-parse v0.2.1
   Compiling errno v0.3.1
   Compiling rustix v0.38.4
   Compiling is-terminal v0.4.9
   Compiling anstream v0.3.2
   Compiling clap_builder v4.3.19
   Compiling clap v4.3.19
   Compiling scaffold v0.1.0 (/rust-adventure/scaffold)
    Finished dev [unoptimized + debuginfo] target(s) in 2.55s
     Running `target/debug/scaffold`
[src/main.rs:22] args = Args {
    layout: "post",
    tags: [],
    title: "A Post",
    status: "draft",
    output_dir: "content",
}

There are three major pieces to this program.

  • Our Args struct
  • The clap derive macros
  • Our main function

The Args struct

structs are one way for us to define our own data types in Rust. They’re like objects with string keys and typed values.

Without the Clap dressings, we can see an Args struct with five fields. Each field is either a String or a Vec<String>.

struct Args {
    layout: String,
    tags: Vec<String>,
    title: String,
    status: String,
    output_dir: String,
}

This is exactly what we’ll get when we parse the arguments later: a value of type Args with each of the fields filled out.

clap’s derive macros

derive macros generate code. They’re often used to generate what would otherwise be mundane and generic code, such as how we’re deriving an implementation for Debug here.

The Debug derive macro generates code that implements code that satisfies the Debug trait. This code is unremarkable and basically the same for any struct, so we don’t want to keep writing it over and over.

Using the dbg! macro requires that the value we’re printing implements the Debug trait, so deriving Debug allows us to generate that code.

The Debug derive macro is provided by Rust itself, but crates can also provide their own.

clap::Parser

The clap crate provides a derive macro we can use to generate the code that would otherwise parse the arguments passed to our CLI. It does this by using the Parser derive macro and the additional information we give using the clap helper attribute on each field.

We place the Parser derive macro on our Args struct, which the macro can then read and process.

The Parser derive macro will generate some code that implements the Parser trait for our Args struct and thus associates the relevant functions on the Parser trait with our Args struct.

This trait requires implementing a parse function, so in our main function we can run Args::parse to parse the arguments since we just generated the code powering that function.

clap()

The clap helper allows us to define a flag and a default value for each field.

In our case, we define a short flag, a long flag, and sometimes a default value on each field.

Short flags are single characters. A short flag s would show up as -s when passing arguments to our CLI.

long flags are full names. A long flag named scores would show up as --scores in our CLI.

fields can have short names and long names at the same time, or they can be a positional argument.

The main function

We’ve generated the code that powers the parse function, so we can use Args::parse in our main function.

fn main() {
    let args = Args::parse();
    dbg!(args);
}

This function returns one of our Args structs populated with all the values clap grabbed from the arguments.

We’ve derived Debug on Args, so we can print out the values that got populated with dbg!.

Running the CLI

We can use cargo run as many times as we want. Notice how the Vec flag for tags behaves differently because it is a Vec. Clap’s handling of Vecs means that we get to use the flag multiple times.

We can also specify the title of the post, an arbitrary layout, and whether the post should be published or not.

❯ cargo run -- -t bevy -t rust -t shaders -T "New shaders in Bevy"
    Finished dev [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/scaffold -t bevy -t rust -t shaders -T 'New shaders in Bevy'`
[src/main.rs:22] args = Args {
    layout: "post",
    tags: [
        "bevy",
        "rust",
        "shaders",
    ],
    title: "New shaders in Bevy",
    status: "draft",
    output_dir: "content",
}

If this is your first time, it may be easier to play with flags using the actual binary instead of using the -- after cargo run.

The binary is located in target/debug/scaffold because cargo run also transparently runs cargo build for us.

❯ ./target/debug/scaffold
[src/main.rs:22] args = Args {
    layout: "post",
    tags: [],
    title: "A Post",
    status: "draft",
    output_dir: "content",
}
❯ ./target/debug/scaffold -t bevy -t rust -t shaders -T "New shaders in Bevy"
[src/main.rs:22] args = Args {
    layout: "post",
    tags: [
        "bevy",
        "rust",
        "shaders",
    ],
    title: "New shaders in Bevy",
    status: "draft",
    output_dir: "content",
}

With the arguments parsing, let’s move on to defining the help text.