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

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);
}
}

Related

How do I modify values in a HashMap in 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;

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

Borrowing error when pushing reference into vector that is on the same scope [duplicate]

For reasons related to code organization, I need the compiler to accept the following (simplified) code:
fn f() {
let mut vec = Vec::new();
let a = 0;
vec.push(&a);
let b = 0;
vec.push(&b);
// Use `vec`
}
The compiler complains
error: `a` does not live long enough
--> src/main.rs:8:1
|
4 | vec.push(&a);
| - borrow occurs here
...
8 | }
| ^ `a` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
error: `b` does not live long enough
--> src/main.rs:8:1
|
6 | vec.push(&b);
| - borrow occurs here
7 | // Use `vec`
8 | }
| ^ `b` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
However, I'm having a hard time convincing the compiler to drop the vector before the variables it references. vec.clear() doesn't work, and neither does drop(vec). mem::transmute() doesn't work either (to force vec to be 'static).
The only solution I found was to transmute the reference into &'static _. Is there any other way? Is it even possible to compile this in safe Rust?
Is it even possible to compile this in safe Rust?
No. What you are trying to do is inherently unsafe in the general case.
The collection contains a reference to a variable that will be dropped before the collection itself is dropped. This means that the destructor of the collection has access to references that are no longer valid. The destructor could choose to dereference one of those values, breaking Rust's memory safety guarantees.
note: values in a scope are dropped in the opposite order they are created
As the compiler tells you, you need to reorder your code. You didn't actually say what the limitations are for "reasons related to code organization", but the straight fix is:
fn f() {
let a = 0;
let b = 0;
let mut vec = Vec::new();
vec.push(&a);
vec.push(&b);
}
A less obvious one is:
fn f() {
let a;
let b;
let mut vec = Vec::new();
a = 0;
vec.push(&a);
b = 0;
vec.push(&b);
}
That all being said, once non-lexical lifetimes are enabled, your original code will work! The borrow checker becomes more granular about how long a value needs to live.
But wait; I just said that the collection might access invalid memory if a value inside it is dropped before the collection, and now the compiler is allowing that to happen? What gives?
This is because the standard library pulls a sneaky trick on us. Collections like Vec or HashSet guarantee that they do not access their generic parameters in the destructor. They communicate this to the compiler using the unstable #[may_dangle] feature.
See also:
Moved variable still borrowing after calling `drop`?
"cannot move out of variable because it is borrowed" when rotating variables

How do I add references to a container when the borrowed values are created after the container?

For reasons related to code organization, I need the compiler to accept the following (simplified) code:
fn f() {
let mut vec = Vec::new();
let a = 0;
vec.push(&a);
let b = 0;
vec.push(&b);
// Use `vec`
}
The compiler complains
error: `a` does not live long enough
--> src/main.rs:8:1
|
4 | vec.push(&a);
| - borrow occurs here
...
8 | }
| ^ `a` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
error: `b` does not live long enough
--> src/main.rs:8:1
|
6 | vec.push(&b);
| - borrow occurs here
7 | // Use `vec`
8 | }
| ^ `b` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
However, I'm having a hard time convincing the compiler to drop the vector before the variables it references. vec.clear() doesn't work, and neither does drop(vec). mem::transmute() doesn't work either (to force vec to be 'static).
The only solution I found was to transmute the reference into &'static _. Is there any other way? Is it even possible to compile this in safe Rust?
Is it even possible to compile this in safe Rust?
No. What you are trying to do is inherently unsafe in the general case.
The collection contains a reference to a variable that will be dropped before the collection itself is dropped. This means that the destructor of the collection has access to references that are no longer valid. The destructor could choose to dereference one of those values, breaking Rust's memory safety guarantees.
note: values in a scope are dropped in the opposite order they are created
As the compiler tells you, you need to reorder your code. You didn't actually say what the limitations are for "reasons related to code organization", but the straight fix is:
fn f() {
let a = 0;
let b = 0;
let mut vec = Vec::new();
vec.push(&a);
vec.push(&b);
}
A less obvious one is:
fn f() {
let a;
let b;
let mut vec = Vec::new();
a = 0;
vec.push(&a);
b = 0;
vec.push(&b);
}
That all being said, once non-lexical lifetimes are enabled, your original code will work! The borrow checker becomes more granular about how long a value needs to live.
But wait; I just said that the collection might access invalid memory if a value inside it is dropped before the collection, and now the compiler is allowing that to happen? What gives?
This is because the standard library pulls a sneaky trick on us. Collections like Vec or HashSet guarantee that they do not access their generic parameters in the destructor. They communicate this to the compiler using the unstable #[may_dangle] feature.
See also:
Moved variable still borrowing after calling `drop`?
"cannot move out of variable because it is borrowed" when rotating variables

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