Matching structopt commands and using Rust values after move

Chris Biscardi
InstructorChris Biscardi
Share this video with your friends

Social Share Links

Send Tweet
Published 4 years ago
Updated 4 years ago

We can match on opt.cmd to determine which Command variants was called. This lets us specify the variants as match criteria and also destructure the values out to use them.

    let opt = Opt::from_args();
    dbg!(&opt);
    match opt.cmd {
        Command::Write { title } => todo!(),
    }

As written, we run into two issues with this code. One is that we aren't using title yet. In destructuring, we can use the placeholder (_) in our pattern.

warning: unused variable: `title`
  --> src/main.rs:32:26
   |
32 |         Command::Write { title } => todo!(),
   |                          ^^^^^ help: try ignoring the field: `title: _`
   |
   = note: `#[warn(unused_variables)]` on by default
fn main() -> Result<()> {
    color_eyre::install()?;

    let opt = Opt::from_args();
    dbg!(opt);
    match opt.cmd {
        Command::Write { title: _ } => todo!(),
    }
}

The second issue is that the opt variable is moved before we try to destructure it. If we look at the compiler output before we fixed the title issue we see the value use after move pointing at title specifically.

error[E0382]: use of moved value
m  --> src/main.rs:32:26
   |
29 |     let opt = Opt::from_args();
   |         --- move occurs because `opt` has type `Opt`, which does not implement the `Copy` trait
30 |     dbg!(opt);
   |     ---------- value moved here
31 |     match opt.cmd {
32 |         Command::Write { title } => todo!(),
   |                          ^^^^^ value used here after move

If we look at it after fixing the unused title, we see the compiler point at opt.cmd.

error[E0382]: use of moved value: `opt.cmd`
  --> src/main.rs:31:11
   |
29 |     let opt = Opt::from_args();
   |         --- move occurs because `opt` has type `Opt`, which does not implement the `Copy` trait
30 |     dbg!(opt);
   |     ---------- value moved here
31 |     match opt.cmd {
   |           ^^^^^^^ value used here after move

This is because opt is moved into the dbg call because it doesn't implement the Copy trait. Now, we could implement or derive Copy for Opt if Command can implement Copy, but we can not implement Copy for Command. This is because String does not, and can not, implement Copy, and we have a String in our title.

Without diving into the depths of Copy, Clone, and allocation, there is something we have that already implements Copy, so we don't need to.

Shared references implement Copy.

So instead of passing opt into dbg!, and thus using move semantics (which are the default in Rust), we can pass a shared reference in: dgb!(&opt) which lets us use copy semantics.

The difference between move semantics and copy semantics in this case is that we can access opt after passing it to dbg!.

Under the hood, both a copy and a move can result in bits being copied in memory, although this is sometimes optimized away. -- Copy

fn main() -> Result<()> {
    let opt = Opt::from_args();
    dbg!(&opt);
    match opt.cmd {
        Command::Write { title: _ } => todo!(),
    }
}

Instructor: [0:00] From here, I'm going to use Cargo Watch, which we installed in the last lesson, to run check and then test. To continue the implementation of our CLI, we can match on opt.cmd. That is the StructOpt Command field.

[0:17] Match lets us match the structure of the value that we're matching on. In this case, we can match on the structure of commandWrite, such that whenever the Write variant of the Command enum is used we execute some code. In this case it will be todo, and I'll remove the final todo.

[0:35] Note that we're also destructuring title here, but cargo check gives us an error. We get the error, use of moved value, value used here after move, title. The move occurs because opt has typed Opt. That is the variable name Opt as the type struct of opt, which does not implement the Copy trait.

[0:58] Since we're not using title yet, we can use the placeholder in our pattern to say, "Hey, we're not using this yet. Don't warn us about it." Now, we get a slightly better error. The use of moved value opt.cmd, same move, copy, the value is moved here in dbg opt, and the value is used here after move, in opt.cmd.

[1:25] This is because we passed opt into dbg, and dbg has taken ownership of this value. Because dbg has taken ownership of this value and used it, we can't use it after that. We can fix this by giving dbg a shared reference to opt. A shared reference means that we can access this value later when we want to match on it.

[1:46] What was happening originally, when we didn't have the shared reference, is called move semantics. That is, if we use the variable and we give ownership to the dbg function, or the dbg macro in this case, the variable moves ownership into that function. We don't get it back after it's done.

[2:06] Passing the shared reference in lets us use copy semantics, which means that the ownership won't get moved into this function and that we can still access the variable later.