Reputation: 69512
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
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