How to make values of HashMap mutable?


#1

The title of this question is probably misleading and not completely relevant. I’m new to this language and believe I’m banging my head against the "borrow checker’ and I should fix in my mental mdoel of how Rust deals with ownership, among other things. I may break the one-question-per-question rule in this post.

Here is some incomplete code to build an adjacency list:

type AdjList = HashMap<i32, Vec<i32>>;

fn adj_list(elist: Vec<(i32, i32)>) -> AdjList {
    let mut adjacency_list = AdjList::new();

    for i in elist.iter() {
        if adjacency_list.contains_key(&i.0) {
            adjacency_list.get(&i.0).unwrap().push(i.1);
        } else {
            let v: Vec<i32> = Vec::new();
            adjacency_list.insert(i.0, v);
        }
    }
    adjacency_list
}

A few things of interest are going on here. When I create the adjacency list, I do so by calling the new() constructor on AdjList since AdjList is just a type alias to a HashMap with it’s concrete types already filled in. I attempt to make the AdjList named adjacency_list mutable by using the mut keyword. However, when I attempt to push an element into the vector of integers in this map the compiler says

cannot borrow immutable borrowed content as mutable

How do I make the vector contained in the AdjList, or HashMap, mutable? Why does the mut keyword make the HashMap mutable but not a vector it contains?

Another thing of interest is what happens when one constructs an AdjList. Can someone explain why calling adjacency_list.get( &i.0 ) returns an Option type instead of an empty Vec<i32>?

Thanks for reading


#2

How do I make the vector contained in the AdjList, or HashMap, mutable?

You can use get_mut method of HashMap to get an exclusive access to the value. It seems to be that you come from C++ background. In C++, const overloading happens automatically, but it Rust you usually have two methods, a const and a non-const one.

Can someone explain why calling adjacency_list.get( &i.0 ) returns an Option type instead of an empty Vec?

Because returning a default value would not work for types which do not have a sensible deafault. The ergonomics can be resoted with the entry API (https://doc.rust-lang.org/std/collections/struct.HashMap.html#examples-13):

adjecency_list.entry(&i.0).or_insert(Vec::new()).push(i.1)

BTW, you can use pattern matching in the for loop for &(source, target) in elist.iter()

:slight_smile:


#3

Thanks @matklad, I learned a lot from your response.
Here’s what we have now

fn adj_list(elist: Vec<(i32, i32)>) -> AdjList {
    let mut adjacency_list = AdjList::new();
    for &(source, target) in elist.iter() {
        adjacency_list.entry(source).or_insert(Vec::new()).push(target);
    }
    adjacency_list
}

#4

One more thing! To make it a bit more general, you can use

fn adj_list(elist: &[(i32, i32)]) -> ArgList

That way you want’ impose an allocation on the caller.