How to make values of HashMap mutable?

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

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:

1 Like

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
}

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.

1 Like