34702

How can I borrow from a HashMap to read and write at the same time?

Question:

I have a function f that accepts two references, one mut and one not mut. I have values for f inside a HashMap:

use std::collections::HashMap; fn f(a: &i32, b: &mut i32) {} fn main() { let mut map = HashMap::new(); map.insert("1", 1); map.insert("2", 2); { let a: &i32 = map.get("1").unwrap(); println!("a: {}", a); let b: &mut i32 = map.get_mut("2").unwrap(); println!("b: {}", b); *b = 5; } println!("Results: {:?}", map) }

This doesn't work because HashMap::get and HashMap::get_mut attempt to mutably borrow and immutably borrow at the same time:

<pre class="lang-none prettyprint-override">error[E0502]: cannot borrow `map` as mutable because it is also borrowed as immutable --> src/main.rs:15:27 | 12 | let a: &i32 = map.get("1").unwrap(); | --- immutable borrow occurs here ... 15 | let b: &mut i32 = map.get_mut("2").unwrap(); | ^^^ mutable borrow occurs here ... 18 | } | - immutable borrow ends here

In my real code I'm using a large, complex structure instead of a i32 so it is not a good idea to clone it.

In fact, I'm borrowing two different things mutably/immutably, like:

struct HashMap { a: i32, b: i32, } let mut map = HashMap { a: 1, b: 2 }; let a = &map.a; let b = &mut map.b;

Is there any way to explain to the compiler that this is actually safe code?

I see how it possible to solve in the concrete case with iter_mut:

{ let mut a: &i32 = unsafe { mem::uninitialized() }; let mut b: &mut i32 = unsafe { mem::uninitialized() }; for (k, mut v) in &mut map { match *k { "1" => { a = v; } "2" => { b = v; } _ => {} } } f(a, b); }

But this is slow in comparison with HashMap::get/get_mut

Answer1:

<strong>TL;DR: You will need to change the type of HashMap</strong>

<hr />

When using a method, the compiler does <em>not</em> inspect the interior of a method, or perform any runtime simulation: it only bases its ownership/borrow-checking analysis on the signature of the method.

In your case, this means that:

<ul><li>using get will borrow the entire HashMap for as long as the reference lives,</li> <li>using get_mut will mutably borrow the entire HashMap for as long as the reference lives.</li> </ul>

And therefore, it is not possible with a HashMap<K, V> to obtain both a &V and &mut V at the same time.

<hr />

The work-around, therefore, is to avoid the need for a &mut V entirely.

This can be accomplished by using Cell or RefCell:

<ul><li>Turn your HashMap into HashMap<K, RefCell<V>>,</li> <li>Use get in both cases,</li> <li>Use borrow() to get a reference and borrow_mut() to get a mutable reference.</li> </ul>use std::{cell::RefCell, collections::HashMap}; fn main() { let mut map = HashMap::new(); map.insert("1", RefCell::new(1)); map.insert("2", RefCell::new(2)); { let a = map.get("1").unwrap(); println!("a: {}", a.borrow()); let b = map.get("2").unwrap(); println!("b: {}", b.borrow()); *b.borrow_mut() = 5; } println!("Results: {:?}", map); }

This will add a runtime check each time you call borrow() or borrow_mut(), and will panic if you ever attempt to use them incorrectly (if the two keys are equal, unlike your expectations).

<hr />

As for using fields: this works because the compiler can reason about borrowing status on a per-field basis.

Recommend

  • How to remove every third element in a php array until only one element remains and print that eleme
  • Attaching SSL certificate to Azure application gateway in Terraform
  • On kubernetes helm how to replace a pod with new config values
  • Metadata lost when saving photo using PHPhotoLibrary
  • Distributed Elixir example doesn't work
  • Set Visual Studio Code to be global Git editor on OSX
  • FileUpload + UpdatePanel does not work on first click
  • get widgets by name from layout
  • Do warp vote functions synchronize threads in the warp?
  • how to solve access denied when using UWP GetFolderFromPathAsync
  • Rails 3.2.1 - cannot parse Cookie header: undefined method `size' for nil:NilClass
  • Decimal Presentation : different purpose between zoned decimal and packed decimal
  • layer popup with jQuery
  • RestSharp PUT XML, RestSharp is sending it as GET?
  • Azure function C#: Create or replace document in cosmos db on HTTP request
  • Byte Array to *Signed* Int
  • How to use Sanitize on HTML Entity
  • touch events not working in android phonegap webview (or even built-in browser)
  • CRM Dynamics How to set short list - long list relationship
  • how to change button text after succes in ajax
  • Fetch data from nested nodes in Firebase
  • PHP users local time
  • Dynamic reference casting depending on actual object type
  • Python tk scrollbar becomes inactive once text is outside the screen
  • Get all categories and items in category
  • How to get WinForms custom control's default value to be respected when first dropped on a form
  • SqlDatasource select parameters
  • How does the dispatcher work when mixing sync/async with serial/concurrent queue?
  • Django REST framework - HyperlinkedRelatedField with additional parameter
  • First dynamically-added TinyMCE editor displays, others do not
  • 2d barcode reader Java ME sdk
  • How to redirect into different page by user type in php and mysql
  • Possible to set default CloudKit container not based on application name?
  • Google Spreadsheet Script to Blink a range of Cells
  • Terminal run dalvikvm with am.jar
  • How to handle div that is created dynamically in a table
  • Make checkout phone field optional for specific countries in WooCommerce
  • How to use FirstOrDefault inside Include
  • ReferenceError: TextEncoder is not defined