Reputation: 3221
Would someone be kind enough to explain to me why using a String
in this script does not work but &str
does. Additionally how can I modify it so that it will work with String
's? [version 1.2]
use std::collections::{HashMap};
fn main() {
let mut hash = HashMap::<&str, &str>::new();
hash.insert("this", "value");
let l: &str = "this is a borrowed string reference";
// If the above line was defined as:
//let l: String = "this is a string".to_string();
let mut all = l.split(" ");
let name: &str = all.next().unwrap();
if hash.contains_key(name) == true {
hash.remove(name);
} else {
hash.insert(name, "stuff");
}
}
Upvotes: 3
Views: 218
Reputation: 42828
If you change l
to String
you get:
error:
l
does not live long enough
Which is true:
name
is a reference to one of the substrings of l
. You're possibly inserting the name
reference to hash
, but the lifetime of hash
is longer than the lifetime of l
. Therefore when the lifetime of l
ends, hash
would contain invalid references. This is not allowed. F.ex. if you remove the insert
line, Rust is happy.
There are multiple ways to fix it, depending on your needs. One of them would be to make the lifetime of hash
shorter than the lifetime of l
, by instantiating hash
after l
:
use std::collections::{HashMap};
fn main() {
// let l: &str = "this is a borrowed string reference";
// If the above line was defined as:
let l: String = "this is a string".to_string();
let mut hash = HashMap::<&str, &str>::new();
hash.insert("this", "value");
let mut all = l.split(" ");
let name: &str = all.next().unwrap();
if hash.contains_key(name) == true {
hash.remove(name);
} else {
hash.insert(name, "stuff");
}
}
Or, you could store copies of the strings in the map.
Upvotes: 2
Reputation: 299790
You have a lifetime issue.
Any object inserted into hash
must only reference things that outlive hash
(or not reference anything at all).
However, here, you define l
after hash
and therefore l
has a shorter lifetime. In turn this means that name
which references l
's buffer has a shorter lifetime than hash
and thus name
is not suitable to be inserted into hash
(though it can be used to search/remove).
Switching the order in which l
and hash
are defined make it work:
use std::collections::{HashMap};
fn main() {
let l: String = "this is a string".to_string();
let mut hash = HashMap::<&str, &str>::new();
hash.insert("this", "value");
let mut all = l.split(" ");
let name: &str = all.next().unwrap();
if hash.contains_key(name) == true {
hash.remove(name);
} else {
hash.insert(name, "stuff");
}
}
If this is not possible, then use a HashMap<String, String>
to avoid lifetime issues.
Upvotes: 1
Reputation: 58975
Ok, let's boil this down to just the necessities:
use std::collections::HashMap;
fn main() {
let mut hash = HashMap::<&str, &str>::new();
hash.insert("this", "value");
let l: String = "this is a borrowed string reference".to_string();
hash.insert(&l, "stuff");
}
Compiling this gives us:
<anon>:7:18: 7:19 error: `l` does not live long enough
<anon>:7 hash.insert(&l, "stuff");
^
<anon>:4:49: 8:2 note: reference must be valid for the block suffix following statement 0 at 4:48...
<anon>:4 let mut hash = HashMap::<&str, &str>::new();
<anon>:5 hash.insert("this", "value");
<anon>:6 let l: String = "this is a borrowed string reference".to_string();
<anon>:7 hash.insert(&l, "stuff");
<anon>:8 }
<anon>:6:71: 8:2 note: ...but borrowed value is only valid for the block suffix following statement 2 at 6:70
<anon>:6 let l: String = "this is a borrowed string reference".to_string();
<anon>:7 hash.insert(&l, "stuff");
<anon>:8 }
Which tells you more or less exactly what it won't work. You try to insert a borrowed pointer to the String
l
into the hashmap. However, the String
simply doesn't live long enough.
Specifically, values are destroyed by Rust in reverse lexical order. So, when execution gets to the end of the function, Rust will deallocate l
first, followed by hash
. This is a problem: it means that there is a window during which hash
contains pointers to destroyed data, which Rust absolutely will not allow.
The reason this works with a &str
is that a string literal "like this"
is not merely &str
; it's actually &'static str
. This means that a string literal "lives" for the entire duration of the program: it never gets destroyed, thus it's safe for the hashmap to hold a pointer to it.
The solution is to ensure the String
will outlive the HashMap
:
use std::collections::HashMap;
fn main() {
// Declare `l` here ...
let l;
let mut hash = HashMap::<&str, &str>::new();
hash.insert("this", "value");
// ... but initialise it *here*.
l = "this is a borrowed string reference".to_string();
hash.insert(&l, "stuff");
}
Now, hash
gets destroyed first, followed by l
. It's fine to leave a variable uninitialised, so long as you do initialise it prior to reading or using it.
Upvotes: 5