We can process our subcommand now using a match on the Args.cmd
field we defined earlier.
This is just a normal struct (Args
) with a field that is a normal enum (cmd
). There is nothing magical here, so match
allows us to match the variants we defined in Commands
and run some logic based on which one came through.
We also take this opportunity to destructure title
out of the Write
variant struct.
dbg!(garden_path);
match args.cmd {
Commands::Write { title } => {
dbg!(title);
}
}
Running this code shows us the dbg!
macros we’ve set up through our program all the way through to the Write
command.
❯ cargo run -- write -t "My New Post"
Compiling garden v0.1.0 (/rust-adventure/digital-garden)
Finished dev [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/garden write -t 'My New Post'`
[src/main.rs:42] &args = Args {
garden_path: None,
cmd: Write {
title: Some(
"My New Post",
),
},
}
[src/main.rs:68] garden_path = "/Users/chris/garden"
[src/main.rs:71] title = Some(
"My New Post",
)
Since that’s all working, I’m going to remove the dbg!
macros to clean up my output.
library crates and binary crates
A crate is the unit of compilation in Rust. Our main.rs
is the entrypoint to our binary crate, but we can also have a library crate.
It is often the case that you’ll want the binary crate to contain the CLI definition and you’ll want your CLI’s logic to be contained in a library crate.
This is how you get a binary that can run as well as a library that you can bring into scope with use
.
Let’s create the default library crate entrypoint, which is conventionally named lib.rs
.
Inside of src/lib.rs
we can write a new function called write
that accepts the garden_path
and the title
as arguments, then dbg!
s the values out.
use std::path::PathBuf;
pub fn write(garden_path: PathBuf, title: Option<String>) {
dbg!("in write", garden_path, title);
}
We’ve defined this function as pub
, so it will be accessible to our binary crate in main.rs
.
Our library name is implicitly garden
because that’s what we named the package at the beginning of the workshop. This means we can access the newly public function write
that exists in the garden
crate with the module path garden::write
.
this is just like using any other crate.
match args.cmd {
Commands::Write { title } => {
garden::write(garden_path, title)
}
}
Running the binary now results in the function from our library being called.
❯ cargo run -- write -t "My New Post"
Compiling garden v0.1.0 (/rust-adventure/digital-garden)
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/garden write -t 'My New Post'`
[src/lib.rs:4] "in write" = "in write"
[src/lib.rs:4] garden_path = "/Users/chris/garden"
[src/lib.rs:4] title = Some(
"My New Post",
)
When it comes down to it, your cargo project can have as many binary crates as you want to have, but it can only have one library crate.
The biggest benefit of moving our logic to lib.rs
is that you can’t directly test the main function in main.rs
.
You can integration test the whole binary, or you can unit test individual functions, but you can’t use the main function in a test.
You can however integration test against the public interface you expose via your library crate.
So a convention has developed where main.rs
usually contains the argument parsing execution and any environment setup, and then you call into your library to actually run any logic your tool is meant to do.