Trouble with mutable reference inside of a generic

I am having a little trouble with the following code:

use std::collections::HashMap;

fn main() {
    let mut table: Vec<HashMap<&str, Vec<&str>>> = Vec::new();
    let mut stack: Vec<&str> = Vec::new();
    table.push(HashMap::new());
    table[0].insert("baz", vec!["foo", "bar"]);
    stack.append(table[0].get("baz").unwrap());
}

This throws a compiler error: types differ in mutability where I called stack.append. Changing the type of table to Vec<HashMap<&str, &mut Vec<&str>>> causes all sorts of other problems, namely invalidating the insert line (and having to use a variable with a let binding because the vec! lifetime isnt long enough). Is there any way around this? Currently I just write a loop and append each item in the vector individually.

Try extend instead of append. extend is provided by the Extend trait (already in the prelude, no need to import it), and accepts arbitrary iterables.

use std::collections::HashMap;

fn main() {
    let mut table: Vec&lt;HashMap&lt;&str, Vec&lt;&str&gt;&gt;&gt; = Vec::new();
    let mut stack: Vec&lt;&str&gt; = Vec::new();
    table.push(HashMap::new());
    table[0].insert("baz", vec!["foo", "bar"]);
    stack.extend(table[0].get("baz").unwrap());
}

append on the other hand is a destructive operation which wants to devour its argument. Specifically, the signature of append asks for the second argument to be &mut Vec, and this is why you were getting type errors. (Actually, I never even realized there was such a function as append, simply because I've never needed it. It seems unfortunate to me that it has such a vague and innocent sounding name...)

(actually, I have done stuff like append, but I prefer to write it as a.extend(b.drain(..)), which is much more evocative of what's actually happening)

Ahh fantastic! This should definitely be included in the Rust book.

If I wanted to drain the vector instead, what should my generic declaration be? Vec<HashMap<mut Vec<&str>>> throws an error

Type stays the same - Vec<HashMap<&str, Vec<&str>>> - but you would change table[0].get(...) to table[0].get_mut(...).

1 Like

Oh okay I wasn't aware of this method.
Thank you guys for your help! This forum is really great!

BTW, if you're just learning Rust and you're not 100% sure you specifically want HashMap with &str, then in real-world code you'll most likely want HashMap of String. The &str version is a special case that will cause a lot of annoyance if you try to use it for anything else besides just string literals in main().

2 Likes

Alright I will keep that in mind. Any specific reason why I should avoid this? Is it just that string literals are finicky in general or that they don't play nice with rust?

Memory of string literals is never freed (until end of the program), so they're an "easy" case of references. They "live" forever, so the borrow checker doesn't care much about them.

However, as soon as you start using any dynamically-generated string, or string allocated in some struct, you'll discover their not living forever like the literals and the borrow checker will have to start being strict about the temporary lifetime of borrowed &str. For example, if you put content of a variable (that's not initialized from literal) in HashMap<&str>, you won't be able to use the hash map outside the block where the variable is — the lifetime will tie the variable and the hashmap to each other.

OTOH String is an owned type. It lives as long as it needs to, and it's freed only when it's no longer used. If you put it in a HashMap, it will by definition live as long as the HashMap, allowing unrestricted use of the HashMap.

Basically, &str is a zero-copy abstraction, and the gains in performance are paid for in developer hours.

This doesn't mean you should never use them. &str is a great choice for an individual string taken as a function argument, because they're easy to obtain from the caller's perspective (eg. &String coerces into &str) and will obviously outlive the function call. And there's nothing unusual about an iterator producing &strs (such as str's own split which returns zero-copy slices of the input string).

But once you start putting strings into collections that you may want to be passing around and/or returning from functions, you will find it is far easier to stick to owned data.

1 Like