How do I modify values in a HashMap in Rust? - rust

If I had a Hashmap in Rust and I wanted to get a value and modify it, for example, lets say the value is a type u32 and I want to increment it, what is the best way to do that? I found an example that works but I am wondering of there a "Best Practices" way to do it. The example I found that does the job is:
`use std::collections::HashMap;
use std::cell::RefCell;
fn main() {
let mut map = HashMap::new();
map.insert("Key".to_owned(), RefCell::new(0));
let value = map.get("Key").unwrap();
*value.borrow_mut() += 1;
println!("{:?}", value.borrow());
}`
Which worked for me, but I was suspicious of using RefCell for this. Is there a better way to do it? Thanks.

Yeah, I'm suspicious of that RefCell too. You'd use that if you had a very specific requirement, such as the interior mutability capabilities of RecCell.
I don't see why you can't just use the example code and ditch the RefCell.
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert("Key".to_owned(), 0);
let value = map.get_mut("Key").unwrap();
*value += 1;
println!("{:?}", value);
let read_value = map.get("Key").unwrap();
println!("{:?}", read_value);
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a23a90c4df7eb010945980b9b95eb031

RefCell provides run time borrow checking, as opposed to compile time borrow checking which you would otherwise get.
In many cases you do not need that - you can just use get_mut as suggested in the comments and by #cadolphs.
However if you need to get mutable access to individual elements within the map at the same time, you might use RefCell. For example, consider this code:
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert("Key".to_owned(), 0);
map.insert("Key2".to_owned(), 1);
let value = map.get_mut("Key").unwrap();
let value2 = map.get("Key2").unwrap();
*value += *value2;
println!("{:?}", *value);
}
This will fail to compile because I am trying to get a second value from the hashmap while I am still holding a mutable reference to the first:
error[E0502]: cannot borrow `map` as immutable because it is also borrowed as mutable
--> src/main.rs:8:18
|
7 | let value = map.get_mut("Key").unwrap();
| ------------------ mutable borrow occurs here
8 | let value2 = map.get("Key2").unwrap();
| ^^^^^^^^^^^^^^^ immutable borrow occurs here
9 | *value += *value2;
| ----------------- mutable borrow later used here
You could solve that using RefCell like this:
use std::collections::HashMap;
use std::cell::RefCell;
fn main() {
let mut map = HashMap::new();
map.insert("Key".to_owned(), RefCell::new(0));
map.insert("Key2".to_owned(), RefCell::new(1));
let value = map.get("Key").unwrap();
let value2 = map.get("Key2").unwrap();
*value.borrow_mut() += *value2.borrow();
println!("{:?}", *value);
}
Here we can get the value we intend to modify out of the hash using get rather than get_mut, so we are not borrowing the hash mutably. The hash itself is not being modified, just the values inside it - this is the pattern referred to in the Rust community as interior mutability.
This pattern should be used very sparingly though, only when really needed.
For one thing, you are trading a compile time check for a run time check. If you have made a mistake in your logic, you won't find out at compile time, you will find out when the code panics at runtime! You can work around that by using the try_borrow* versions of these methods (eg. try_borrow_mut), which return a Result instead of panicing, but then you need to add error handling to deal with it.
Another reason is that a run time borrow check may harm the performance of your code.
My example above is a case where you can easily avoid the whole thing, because the values in the hashmap are just integers which are Copy, so we can just do this instead:
let value2 = *map.get("Key2").unwrap();
let value = map.get_mut("Key").unwrap();
*value += value2;

Related

Lifetime problem with HashMap with HashMap references as values

(New to rust, FYI!)
So - I've been trying to grok the concept of lifetimes in Rust. I've read the documentation, and read some blogs and SO posts on the topic. But still not quite getting it (hence, possibly a bad title to the question).
I've got a particular problem I'm trying to figure out, which I've boiled down to this small sample code (tried to make as close to a working sample as I could):
use std::collections::HashMap;
// Imagine this is actually a more complex type:
type BigComplexType = i32;
fn some_expensive_computation() -> BigComplexType {
// Imagine this takes some arguments, and takes a long time to run
return 123;
}
fn construct() -> HashMap<i32, &'static HashMap<i32, BigComplexType>> {
let mut main = HashMap::new();
let mut nested = HashMap::new();
nested.insert(1111, some_expensive_computation());
nested.insert(2222, some_expensive_computation());
// ... and lots more inserts...
main.insert(10, &nested);
main.insert(20, &nested);
// ... and lots more inserts...
// Imagine a lot more other nested HashMaps to follow here
// let mut nested2 = ...
// ...
// main.insert(..., &nested2);
// ...
return main;
}
fn main() {
construct();
}
This example is a bit trivial - in the actual code, I'm creating much more complicated and deeper structures in the construct() function.
What I'm trying to do is to create some sort of cache that holds these pre-computed values, so that they can be easily and quickly accessed else in the code. Perhaps this can all just be done in some totally different way - but I figured there must be a way to do it this way.
But, rustc complains here, because nested only exists in construct(), and once we're out of the function, it ceases to exist, and thus all references are invalid (or, that's how I understand the problem).
I've tried to introduce a 'a lifetime to the construct() function, and use that lifetime on the nested HashMap below but no dice. Got some errors, and could never fully make it work. I've tried all sorts of variants of adding lifetime annotations, but no dice.
I have a feeling I'm just not grokking some aspect of the whole concept here. Can anyone help point me in the right direction?
(I did think about gathering the nested HashMap into a vector and returning along side the main hashmap - so that then the function is returning the main HashMap, plus a vector of the nested ones - thus, the lifetime would be guaranteed, I think - but I hit some other roadblocks trying that, and have a feeling I'm over-complicating things.)
For reference, this is the error I get in compiling the above:
error[E0515]: cannot return value referencing local variable `nested`
--> lifetime_return.rs:29:12
|
19 | main.insert(10, &nested);
| ------- `nested` is borrowed here
...
29 | return main;
| ^^^^ returns a value referencing data owned by the current function
error[E0515]: cannot return value referencing local variable `nested`
--> lifetime_return.rs:29:12
|
20 | main.insert(20, &nested);
| ------- `nested` is borrowed here
...
29 | return main;
| ^^^^ returns a value referencing data owned by the current function
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0515`.
Any/all help would be greatly appreciated! I tried to look for similar question, but couldn't spot one - perhaps I just didn't recognize some SO post as a duplicate, as I don't fully understand the lifetime model just yet.
Your intuition for why this doesn't work is correct: nested lives only inside construct, and you try to return references to it in the hashmap that live for longer than the function. Assuming that you don't want to clone the nested maps, presumably because they're very large, you can use Rc instead as a way to have trivially cloneable references to the nested maps that keep them alive for as long as necessary:
use std::collections::HashMap;
use std::rc::Rc;
type BigComplexType = i32;
fn some_expensive_computation() -> BigComplexType {
return 123;
}
fn construct() -> HashMap<i32, Rc<HashMap<i32, BigComplexType>>> {
let mut main = HashMap::new();
let mut nested = HashMap::new();
nested.insert(1111, some_expensive_computation());
nested.insert(2222, some_expensive_computation());
let nested_rc = Rc::new(nested);
main.insert(10, Rc::clone(&nested_rc));
main.insert(20, nested_rc); // can move the Rc for the last insert
let mut nested2 = HashMap::new();
nested2.insert(3333, some_expensive_computation());
nested2.insert(4444, some_expensive_computation());
let nested2_rc = Rc::new(nested2);
main.insert(30, Rc::clone(&nested2_rc));
main.insert(40, nested2_rc);
return main;
}
fn main() {
let map = construct();
println!("{}", map[&10][&1111]); // 123
}
Playground link

Rust check borrow with the whole HashMap, not check the key, is there any good way?

I want move the elements of HashMap<u64, Vec> key=1 to key=2
use std::collections::HashMap;
fn main() {
let mut arr: HashMap<u64, Vec<u64>> = HashMap::new();
arr.insert(1, vec![10, 11, 12]); // in fact, elments more than 1,000,000, so can't use clone()
arr.insert(2, vec![20, 21, 22]);
// in fact, following operator is in recusive closure, I simplify the expression:
let mut vec1 = arr.get_mut(&1).unwrap();
let mut vec2 = arr.get_mut(&2).unwrap();
// move the elements of key=1 to key=2
for v in vec1 {
vec2.push(vec1.pop().unwrap());
}
}
got error:
error[E0499]: cannot borrow `arr` as mutable more than once at a time
--> src/main.rs:10:20
|
9 | let mut vec1 = arr.get_mut(&1).unwrap();
| --- first mutable borrow occurs here
10 | let mut vec2 = arr.get_mut(&2).unwrap();
| ^^^ second mutable borrow occurs here
11 | for v in vec1 {
| ---- first borrow later used here
Rust check borrow with the whole HashMap, not check the key.
Is there any good way ?
It's not clear what the context / constraints are, so depending on those there are various possibilities of different impact and levels of complexity
if you don't care about keeping an empty version of the first entry, you can just use HashMap::remove as it returns the value for the removed key: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1734142acb598bad2ff460fdff028b6e
otherwise, you can use something like mem::swap to swap the vector held by key 1 with an empty vector, then you can update the vector held by key 2: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e05941cb4d7ddf8982baf7c9437a0446
because HashMap doesn't have splits, the final option would be to use a mutable iterator, iterators are inherently non-overlapping so they provide mutable references to individual values, meaning they would let you obtain mutable references to both values simultanously, though the code is a lot more complicated: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=87d3c0a151382ce2f47dda59dc089d70
While the third option has to traverse the entire hashmap (which is less efficient than directly finding the correct entries by hashing), it has the possible advantage of not "losing" v1's allocation which is useful if v1 will be filled again in the future: in the first option v1 is completely dropped, and in the second option v1 becomes a vector of capacity 0 (unless you swap in a vector with a predefined capacity, but that's still an extra allocation)
You can put the Vec into a RefCell moving the borrow check to runtime:
use std::cell::RefCell;
use std::collections::HashMap;
fn main() {
let mut arr: HashMap<u64, RefCell<Vec<u64>>> = HashMap::new();
arr.insert(1, RefCell::new(vec![10, 11, 12])); // in fact, elments more than 1,000,000, so can't use clone()
arr.insert(2, RefCell::new(vec![20, 21, 22]));
// in fact, following operator is in recusive closure, I simplify the expression:
let mut vec1 = arr.get(&1).unwrap().borrow_mut();
let mut vec2 = arr.get(&2).unwrap().borrow_mut();
// move the elements of key=1 to key=2
vec2.append(&mut vec1);
}
Tip: Use Vec::append which moves the values from one vector to another.

Insert into a HashMap based on another value in the same Hashmap

I'm trying to insert a value into a HashMap based on another value in the same HashMap, like so:
use std::collections::HashMap;
fn main() {
let mut some_map = HashMap::new();
some_map.insert("a", 1);
let some_val = some_map.get("a").unwrap();
if *some_val != 2 {
some_map.insert("b", *some_val);
}
}
which gives this warning:
warning: cannot borrow `some_map` as mutable because it is also borrowed as immutable
--> src/main.rs:10:9
|
7 | let some_val = some_map.get("a").unwrap();
| -------- immutable borrow occurs here
...
10 | some_map.insert("b", *some_val);
| ^^^^^^^^ --------- immutable borrow later used here
| |
| mutable borrow occurs here
|
= note: `#[warn(mutable_borrow_reservation_conflict)]` on by default
= warning: this borrowing pattern was not meant to be accepted, and may become a hard error in the future
= note: for more information, see issue #59159 <https://github.com/rust-lang/rust/issues/59159>
If I were instead trying to update an existing value, I could use interior mutation and RefCell, as described here.
If I were trying to insert or update a value based on itself, I could use the entry API, as described here.
I could work around the issue with cloning, but I would prefer to avoid that since the retrieved value in my actual code is somewhat complex. Will this require unsafe code?
EDIT
Since previous answer is simply false and doesn't answer the question at all, there's code which doesn't show any warning (playground)
Now it's a hashmap with Rc<_> values, and val_rc contains only a reference counter on actual data (number 1 in this case). Since it's just a counter, there's no cost of cloning it. Note though, that there's only one copy of a number exists, so if you modify a value of some_map["a"], then some_map["b"] is modified aswell, since they refer to a single piece of memory. Also note, that 1 lives on stack, so you better consider turn it into Rc<Box<_>> if you plan to add many heavy objects.
use std::collections::HashMap;
use std::rc::Rc;
fn main() {
let mut some_map = HashMap::new();
some_map.insert("a", Rc::new(1));
let val_rc = Rc::clone(some_map.get("a").unwrap());
if *val_rc != 2 {
some_map.insert("b", val_rc);
}
}
Previous version of answer
Hard to tell what exactly you're looking for, but in this particular case, if you only need to check the value, then destroy the borrowed value, before you update the hashmap. A dirty and ugly code would be like this:
fn main() {
let mut some_map = HashMap::new();
some_map.insert("a", 1);
let is_ok = false;
{
let some_val = some_map.get("a").unwrap();
is_ok = *some_val != 2;
}
if is_ok {
some_map.insert("b", *some_val);
}
}

swapping two entries of a HashMap

i have a simple HashMap; say HashMap<char, char>.
is there a way to swap two elements in this hashmap using std::mem::swap (or any other method)?
Of course there is the simple way getting the values with get and then replace them with insert - but that would trigger the hasher twice (once for getting then for inserting) and i was looking for a way to side-step the second hasher invocation (more out of curiosity than for performance).
what i tried is this (in several versions; none of which worked - and as remarked in the comments: entry would not do what i expect even if i got this past the compiler):
use std::collections::HashMap;
use std::mem::swap;
let mut hash_map: HashMap<char, char> = HashMap::default();
hash_map.insert('A', 'Z');
hash_map.insert('B', 'Y');
swap(&mut hash_map.entry('A'), &mut hash_map.entry('B'));
now the compiler complains (an i understand why it should)
error[E0499]: cannot borrow `hash_map` as mutable more than once at a time
--> tests.rs:103:42
|
103 | swap(&mut hash_map.entry('A'), &mut hash_map.entry('B'));
| ---- -------- ^^^^^^^^ second mutable borrow occurs here
| | |
| | first mutable borrow occurs here
| first borrow later used by call
also just getting the two values this way fails in more or less the same way:
let mut a_val = hash_map.get_mut(&'A').expect("failed to get A value");
let mut b_val = hash_map.get_mut(&'B').expect("failed to get B value");
swap(&mut a_val, &mut b_val);
is there a way to simply swap two entries of a HashMap?
I can't see any safe way to do it:
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert('A', 'Z');
map.insert('B', 'Y');
let a = map.get_mut(&'A').unwrap() as *mut char;
let b = map.get_mut(&'B').unwrap() as *mut char;
unsafe {
std::ptr::swap(a, b);
}
assert_eq!(map.get(&'A'), Some(&'Y'));
assert_eq!(map.get(&'B'), Some(&'Z'));
}
There is one completely safe way I can think of to do this safely, but it's super inefficient: what you want is to get two &mut values, which means borrowck needs to know they're nonoverlapping. Missing a builtin along the lines of split_mut (or the collection being handled specially), the only way I see is to mutably iterate the entire collection, keep refs to the items you're interested in, and swap that:
let mut h = HashMap::new();
h.insert("a", "a");
h.insert("b", "b");
let mut it = h.iter_mut();
let e0 = it.next().unwrap();
let e1 = it.next().unwrap();
std::mem::swap(e0.1, e1.1);
println!("{:?}", h);
It requires a linear traversal of the map until you've found the entries whose values you want to swap though. So even though this has the advantage of not hashing at all edwardw's is answer is probably more practical.

How to end a borrow in a match or if let expression?

I am using a HashMap to store an enum. I'd like to get a value from the HashMap and if the value is a specific enum variant, I'd like to insert a modified copy of the value back in the HashMap.
The code I came up with looks like this:
if let Node::LeafNode(mut leaf_node) = *(self.pages.get(&page).unwrap()) {
let mut leaf_node = leaf_node.clone();
// ...
self.pages.insert(leaf_page,Node::LeafNode(leaf_node));
}
This does not compile because the borrow of self.pages lasts until the end of the if let-block and self.pages.insert is a mutable borrow.
I have tried to shadow the value of the HashMap with a copy of the value, but this does not end the borrow. Usually I would use a {} block to limit the borrow, but this seems to be not possible in match or if let.
What is the idiomatic way to end a borrow so that I can get a new mutable borrow?
This is not possible at the moment. What you want is called non-lexical borrows and it is yet to be implemented in Rust. Meanwhile, you should use Entry API to work with maps - in most cases it should be sufficient. In this particular case I'm not sure if entries are applicable, but you can always do something like
let mut result = None;
if let Some(&Node::LeafNode(ref leaf_node)) = self.pages.get(&page) {
let mut leaf_node = leaf_node.clone();
// ...
result = Some((leaf_page, leaf_node));
}
if let Some((leaf_page, leaf_node)) = result {
self.pages.insert(leaf_page, leaf_node);
}
It is difficult to make the code above entirely correct given that you didn't provide definitions of Node and self.pages, but it should be approximately right. Naturally, it would work only if leaf_page and leaf_node do not contain references to self.pages or self, otherwise you won't be able to access self.pages.
Here is Vladimir's solution using match:
let mut result = match self.pages.get(&page) {
Some(&Node::LeafNode(ref leaf_node)) => Some(leaf_node.clone()),
_ => None,
};
if let Some(leaf_node) = result {
// ...
self.pages.insert(page_number, Node::LeafNode(leaf_node));
};

Resources