Marco Bonelli
Marco Bonelli

Reputation: 69512

Is my variable's lifetime changing due to the addition of an apparently unrelated instruction?

I'm quite new to Rust, and still reading the book while writing some simple program every now and then to test what I'm learning.

Today I tried to write a program suggested as an exercise (more precisely the last one at the end of chapter 8.3). Since I'm still learning and thus pretty slow, I run a new cargo build for almost any new line I add to my main.rs. As of now, it looks like this:

use std::io::{self, Write};
use std::collections::{HashMap, HashSet};

enum Command<'a> {
    Add {name: &'a str, unit: &'a str},
    List {unit: &'a str},
    Exit
}

fn main() {
    let mut units: HashMap<&str, HashSet<&str>> = HashMap::new();

    loop {
        let mut cmd = String::new();
        io::stdin().read_line(&mut cmd).unwrap();

        let cmd = match parse_command(&cmd) {
            Ok(command) => command,
            Err(error) => {
                println!("Error: {}!", error);
                continue;
            }
        };

        match cmd {
            Command::Add {name: new_name, unit: new_unit} => {
                let mut u = units.entry("unit1").or_insert(HashSet::new());
                u.insert(new_name);
            },

            Command::List {unit: target_unit} => {},
            Command::Exit => break
        }
    } // end of loop
} // end of main

fn parse_command<'a>(line: &'a String) -> Result<Command<'a>, &'a str> {
    Ok(Command::Exit)
    // ... still need to write something useful ...
}

Nothing complicated, since that I still haven't even wrote anything inside my parse_command function, which currently only returns a Result::Ok(Command::Exit), but when I try to compile the above code, I get the following error:

error[E0597]: `cmd` does not live long enough
  --> src/main.rs:34:2
   |
17 |            let cmd = match parse_command(&cmd) {
   |                                           --- borrow occurs here
...
34 |    } // end of loop
   |    ^ `cmd` dropped here while still borrowed
35 | } // end of main
   | -  borrowed value needs to live until here

It shouldn't be anything strange to figure out, but I'm quite confused by this error. Yes, I drop cmd at the end of the loop, and that's ok, but why does the borrowed value need to live until the end of main? Anything related to cmd happens inside the loop, why is the borrowed value expected to live longer than that?

Trying to figure out what's wrong, I removed the two lines inside the match arm of Command::Add {...}, so it looks like this:

    match cmd {
        Command::Add {name: new_name, unit: new_unit} => {},
        Command::List {unit: target_unit} => {},
        Command::Exit => break
    }

and, to my surprise, the code compiled with no error (even though I need those lines so this is just a silly test).

I thought that these two lines had nothing to do with my cmd variable, or do they? What's going on here? I am 99% sure that there's something very silly that I'm missing, but can't figure out what it might be by myself. Any help would be really appreciated!

Upvotes: 2

Views: 94

Answers (1)

Shepmaster
Shepmaster

Reputation: 432199

Yes, I drop cmd at the end of the loop, and that's ok

No, it's not, and that's what the compiler is telling you. Rust has done its job and prevented you from inserting memory unsafety into your program.

You allocate a String inside the loop, take a reference to it and make a Command from it. Command only says that it contains references, all of the same lifetime. The code then takes one of those references back out of Command and tries to store it in the HashMap.

After the loop exits, the HashMap would contain a reference to the now-deallocated String, which would be a Very Bad Thing.

Anything related to cmd happens inside the loop

No, it doesn't. You pass the reference to the String to a function. At that point, all bets are off. That function can do anything allowed by the signature, including:

fn parse_command<'a>(line: &'a String) -> Result<Command<'a>, &'a str> {
    Ok(Command::Add {
        name: line,
        unit: line,
    })
}

Your code is equivalent to:

use std::collections::HashSet;

fn main() {
    let mut units = HashSet::new();

    {
        let cmd = String::new();
        units.insert(&cmd);
    }

    println!("{:?}", units);
}

Upvotes: 4

Related Questions