I am trying to define a recursive struct similar to a linked list for a tree traversal. A node has some data and access to its parent. The child node should borrow its parent mutably to ensure exclusive access, and release it once it's dropped. I can define this struct using immutable references, but not when I make the parent reference mutable. When making the parent reference mutable, I am confused by the compiler error and do not understand it.
How can I define the lifetimes for such a recursive structure with a mutable parent reference?
Here is a minimal example. This compiles but uses a readonly reference:
struct Node<'a> {
// Parent reference. `None` indicates a root node.
// I want this to be a mutable reference.
pub parent: Option<&'a Node<'a>>,
// This field just represents some data attached to this node.
pub value: u32,
}
// Creates a root node
// I use a static lifetime since there's no parent for the root so there are no constraints there
fn root_node(value: u32) -> Node<'static> {
Node {
parent: None,
value,
}
}
// Creates a child node
// The lifetimes indicates that the parent must outlive its child
fn child_node<'inner, 'outer: 'inner>(
parent: &'inner mut Node<'outer>,
value: u32,
) -> Node<'inner> {
Node {
parent: Some(parent),
value,
}
}
// An example function using the struct
fn main() {
let mut root = root_node(0);
let mut c1 = child_node(&mut root, 1);
let mut c2 = child_node(&mut c1, 2);
{
let mut c3 = child_node(&mut c2, 3);
let c4 = child_node(&mut c3, 4);
let mut cur = Some(&c4);
while let Some(n) = cur {
println!("{}", n.value);
cur = n.parent;
}
}
{
let c5 = child_node(&mut c2, 5);
let mut cur = Some(&c5);
while let Some(n) = cur {
println!("{}", n.value);
cur = n.parent;
}
}
println!("{}", c2.value);
}
Rust playground: immutable reference
I want a mutable reference, so I tried to replace the Node struct to use a mutable reference:
struct Node<'a> {
// Parent reference. `None` indicates a root node.
// I want this to be a mutable reference.
pub parent: Option<&'a mut Node<'a>>,
// This field just represents some data attached to this node.
pub value: u32,
}
But then I get the following error:
error[E0623]: lifetime mismatch
--> src/main.rs:25:22
|
21 | parent: &'inner mut Node<'outer>,
| ------------------------
| |
| these two types are declared with different lifetimes...
...
25 | parent: Some(parent),
| ^^^^^^ ...but data from `parent` flows into `parent` here
Rust playground: mutable reference
I do not understand the relationship between mutability and data flowing into a field. In the immutable case, I was already requiring the functions to pass mutable/exclusive references. I've been trying various combinations of lifetimes (using a single lifetime, reversing their relationship, etc.) but was unsuccessful.
It is not possible to implement this kind of recursive structure with mutable references due to variance.
The Rustonomicon has a section on variance, with the following table:
| | 'a | T |
|-----------|-----------|-----------|
| &'a T | covariant | covariant |
| &'a mut T | covariant | invariant |
In particular, &'a mut T is invariant with regard to T.
The core issue here is that a Node only knows the lifetimes of its parent, not the lifetime of all its ancestors. Even if in my case I'm just interested in mutating the value field of the ancestor, &mut Node also gives access to modify the parent field of any ancestor up the chain where we don't have access to the precise lifetime.
Here is an example where my struct may causes unsoundness with a mutable parent reference.
The following code would be accepted if T was covariant in &'a mut T:
fn main() {
let mut root: Node<'static> = root_node(0);
// where 'a corresponds to `root`
let mut c1: Node<'a> = child_node(&mut root, 1);
{
let mut evil_root: Node<'static> = root_node(666);
{
// where 'b corresponds to `c1`
let mut c2: Node<'b> = child_node(&mut c1, 2);
// where 'c corresponds to `c2`
let mut c3: Node<'c> = child_node(&mut c2, 3);
// Here is the issue: `c3` knows that its ancestors live at least as long
// as `c2`. But it does not know how long exactly.
// With covariance, the lifetime of `evil_root` would be compatible since
// it outlives `c2`. And because `&mut T` enables to mutate any field
// we could do the following:
let c2_ref: &mut Node<'c> = c3.parent.unwrap();
let c1_ref: &mut Node<'c> = c2_ref.parent.unwrap();
*c1_ref.parent = Some(&mut evil_root);
}
}
// Trying to access the parent of `c1` now causes a read-after-free
println!("{}", c1.parent.unwrap().value);
}
The invariance rule ensures that the code above is rejected by the compiler and there is no unsoundness.
Because &mut allows to modify any field, including ones with references, and because this kind of recursion does not keep track of all the parent lifetimes, it would be unsound.
To safely implement such a recursive struct Rust would need a reference allowing to mutate value (since it has a static lifetime, no issue there) but not parent.
In the minimal example I posted above it could be achieved using immutable references for the parents and placing the node data behind a Cell or RefCell. Another possible solution (but I haven't looked much into it) would be to place the mutable parent references behind a Pin but dereferencing it would be unsafe: I'd have to manually ensure that I am never changing the parent reference.
My actual use case is a bit more complex, so I'll try to instead restructure it to remove the need for the recursive struct by storing my data in a stack backed by a Vec.
Related
I'm trying to learn a bit of Rust through a toy application, which involves a tree data structure that is filled dynamically by querying an external source. In the beginning, only the root node is present. The tree structure provides a method get_children(id) that returns a [u32] of the IDs of all the node's children — either this data is already known, or the external source is queried and all the nodes are inserted into the tree.
I'm running into the following problem with the borrow checker that I can't seem to figure out:
struct Node {
id: u32,
value: u64, // in my use case, this type is much larger and should not be copied
children: Option<Vec<u32>>,
}
struct Tree {
nodes: std::collections::HashMap<u32, Node>,
}
impl Tree {
fn get_children(&mut self, id: u32) -> Option<&[u32]> {
// This will perform external queries and add new nodes to the tree
None
}
fn first_even_child(&mut self, id: u32) -> Option<u32> {
let children = self.get_children(id)?;
let result = children.iter().find(|&id| self.nodes.get(id).unwrap().value % 2 == 0)?;
Some(*result)
}
}
Which results in:
error[E0502]: cannot borrow `self.nodes` as immutable because it is also borrowed as mutable
--> src/lib.rs:19:43
|
18 | let children = self.get_children(id)?;
| ---- mutable borrow occurs here
19 | let result = children.iter().find(|&id| self.nodes.get(id).unwrap().value % 2 == 0)?;
| ---- ^^^^^ ---------- second borrow occurs due to use of `self.nodes` in closure
| | |
| | immutable borrow occurs here
| mutable borrow later used by call
Since get_children might insert nodes into the tree, we need a &mut self reference. However, the way I see it, after the value of children is known, self no longer needs to be borrowed mutably. Why does this not work, and how would I fix it?
EDIT -- my workaround
After Chayim Friedman's answer, I decided against returning Self. I mostly ran into the above problem when first calling get_children to get a list of IDs and then using nodes.get() to obtain the corresponding Node. Instead, I refactored to provide the following functions:
impl Tree {
fn load_children(&mut self, id: u32) {
// If not present yet, perform queries to add children to the tree
}
fn iter_children(&self, id: u32) -> Option<IterChildren> {
// Provides an iterator over the children of node `id`
}
}
Downgrading a mutable reference into a shared reference produces a reference that should be kept unique. This is necessary for e.g. Cell::from_mut(), which has the following signature:
pub fn from_mut(t: &mut T) -> &Cell<T>
This method relies on the uniqueness guarantee of &mut T to ensure no references to T are kept directly, only via Cell. If downgrading the reference would mean the unqiueness could have been violated, this method would be unsound, because the value inside the Cell could have been changed by another shared references (via interior mutability).
For more about this see Common Rust Lifetime Misconceptions: downgrading mut refs to shared refs is safe.
To solve this you need to get both shared references from the same shared reference that was created from the mutable reference. You can, for example, also return &Self from get_children():
fn get_children(&mut self, id: u32) -> Option<(&Self, &[u32])> {
// This will perform external queries and add new nodes to the tree
Some((self, &[]))
}
fn first_even_child(&mut self, id: u32) -> Option<u32> {
let (this, children) = self.get_children(id)?;
let result = children.iter().find(|&id| this.nodes.get(id).unwrap().value % 2 == 0)?;
Some(*result)
}
I have the following code:
#![allow(unused)]
#![allow(unused_must_use)]
use std::collections::HashMap;
#[derive(Clone, Debug)]
struct Product {
name: String,
description: Option<String>,
barcode: String,
price: String
}
fn main() {
println!("Loading product list");
let mut h: HashMap<&str, Vec<Product>> = HashMap::new();
let plastic_bag = Product{ name: "Plastic Bag".to_string(), description: None, barcode: "0001A".to_string(), price: "4.50".to_string() };
let recyclable_bag = Product{ name: "Recyclable Bag".to_string(), description: None, barcode: "0001B".to_string(), price: "15.50".to_string() };
let category = vec![recyclable_bag, plastic_bag];
h.insert("checkout", category);
println!("{:#?}", h);
let mut h = make_free("checkout", &h);
println!("{:#?}", h);
}
fn make_free<'a>(category: &'a str, checkout_category: &'a mut HashMap<&str, Vec<Product>>) -> &'a mut HashMap<&'a str, Vec<Product>> {
let mut category = checkout_category.get_mut(category).unwrap();
for product in category {
product.price = "0.00".to_string();
println!("{:#?}", product);
}
return checkout_category
}
I have a list of pre-defined products filled in. I have a method which I call that I would like to change the prices of the borrowed reference's contents to $0.
I receive the 2 errors:
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:31:38
|
31 | let mut h = make_free("checkout", &h);
| ^^ types differ in mutability
|
= note: expected mutable reference `&mut std::collections::HashMap<&str, std::vec::Vec<Product>>`
found reference `&std::collections::HashMap<&str, std::vec::Vec<Product>>`
error[E0621]: explicit lifetime required in the type of `checkout_category`
--> src/main.rs:45:11
|
36 | fn make_free<'a>(category: &'a str, checkout_category: &'a mut HashMap<&str, Vec<Product>>) -> &'a mut HashMap<&'a str, Vec<Product>> {
| ----------------------------------- help: add explicit lifetime `'a` to the type of `checkout_category`: `&'a mut std::collections::HashMap<&'a str, std::vec::Vec<Product>>`
...
45 | return checkout_category
| ^^^^^^^^^^^^^^^^^ lifetime `'a` required
I'm really confused on how you can add a lifetime specifier to the return statement, and why the types are different.
There are a number of problems with this code and they all relate to Rust's concept of ownership and shared and mutable references. I recommend reading the Understanding Ownership section of the free The Rust Programming Language book or even better the respective sections in "Programming Rust" from O'Reilly.
In summary, the most important rules regarding ownership are as follows:
Values have a single owner. Re-assigning a value to a new variable moves the value and makes the perviously owning variable invalid/unusable. Rust tracks this at compile time. When the variable that owns a value goes out of scope the value is dropped (=deleted).
There can be many shared references (e.g. &HashMap<..>) of a value. When there is one or more shared reference, the value is immutable.
Alternatively, there can be one (and only one) mutable reference (e.g. &mut HashMap<..>) of a value. When a mutable reference exists no other reference (shared or mutable) can exist. Mutable references are unique.
References must never outlive the value they refer to.
(There are ways to bend these rules, but these are the base rules in Rust and important to understand.)
The other part that is confusing in Rust are the differences between String and &str. Again, I recommend reading more on this, but the gist is
String owns a string value on the heap
&str is a reference to a string that somebody else owns.
Now, looking at the code
let mut h: HashMap<&str, Vec<Product>> = HashMap::new();
This part is a little bit weird (it might be what you wanted, but probably not): the variable h owns the HashMap, but the HashMap does not own its keys, it only has references to them. Per rule (4) h must not live longer than any of the keys put into it. In practice, Rust cannot track this, so this hashmap is effectively limited to holding &str references that live for the whole program, these are called &'static str, the most common are string literals.
To get a HashMap which owns its keys (they usual case), you'd use
let mut h: HashMap<String, Vec<Product>> = HashMap::new();
// ^^^^^^-- instead of &str
Now the make_free function wants to modify the hashmap it receives. There are two idiomatic ways of doing this: (a) take ownership of the hashmap and return a new hashmap or (b) take a mutable reference &mut HashMap<..> and modify it in place but don't return anything. In this case using a mutable reference would be more natural:
fn make_free(category: &str, checkout_category: &mut HashMap<String, Vec<Product>>) {
let mut category = checkout_category.get_mut(category).unwrap();
for product in category {
product.price = "0.00".to_string();
println!("{:#?}", product);
}
Notice that you don't need lifetimes in this case, you only need them when you return a reference (which is rare).
Using Strings owned by the hashmap and this version of make_free the code becomes (playground link):
#![allow(unused)]
#![allow(unused_must_use)]
use std::collections::HashMap;
#[derive(Clone, Debug)]
struct Product {
name: String,
description: Option<String>,
barcode: String,
price: String
}
fn main() {
println!("Loading product list");
let mut h: HashMap<String, Vec<Product>> = HashMap::new();
let plastic_bag = Product{ name: "Plastic Bag".to_string(), description: None, barcode: "0001A".to_string(), price: "4.50".to_string() };
let recyclable_bag = Product{ name: "Recyclable Bag".to_string(), description: None, barcode: "0001B".to_string(), price: "15.50".to_string() };
let category = vec![recyclable_bag, plastic_bag];
h.insert("checkout".to_string(), category);
println!("{:#?}", h);
make_free("checkout", &mut h);
println!("{:#?}", h);
}
fn make_free(category: &str, checkout_category: &mut HashMap<String, Vec<Product>>) {
let mut category = checkout_category.get_mut(category).unwrap();
for product in category {
product.price = "0.00".to_string();
println!("{:#?}", product);
}
}
Ownership and lifetime problems aside, your main issue is that the input parameter is missing the lifetime on the first type argument: you have HashMap<&str, Vec<Product>> rather than HashMap<&'a str, Vec<Product>> which is the return type. Therefore the inferred lifetime of checkout_category is not 'a, which is required by the method signature. Adding the missing 'a fixes this.
Next, your function make_free takes an &mut reference but you borrowed h as an & reference. This is easily fixed:
let mut h = make_free("checkout", &mut h);
struct Test {
a: i32,
b: i32,
}
fn other(x: &mut i32, _refs: &Vec<&i32>) {
*x += 1;
}
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let mut refs: Vec<&i32> = Vec::new();
for y in &xes {
refs.push(&y.a);
}
xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
}
Although refs only holds references to the a-member of the elements in xes and the function other uses the b-member, rust produces following error:
error[E0502]: cannot borrow `xes` as mutable because it is also borrowed as immutable
--> /src/main.rs:16:5
|
13 | for y in &xes {
| ---- immutable borrow occurs here
...
16 | xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
| ^^^ mutable borrow occurs here ---- immutable borrow later captured here by closure
Playground
Is there something wrong with the closure? Usually splitting borrows should allow this. What am I missing?
Splitting borrows only works from within one function. Here, though, you're borrowing field a in main and field b in the closure (which, apart from being able to consume and borrow variables from the outer scope, is a distinct function).
As of Rust 1.43.1, function signatures cannot express fine-grained borrows; when a reference is passed (directly or indirectly) to a function, it gets access to all of it. Borrow checking across functions is based on function signatures; this is in part for performance (inference across functions is more costly), in part for ensuring compatibility as a function evolves (especially in a library): what constitutes a valid argument to the function shouldn't depend on the function's implementation.
As I understand it, your requirement is that you need to be able to update field b of your objects based on the value of field a of the whole set of objects.
I see two ways to fix this. First, we can capture all mutable references to b at the same time as we capture the shared references to a. This is a proper example of splitting borrows. A downside of this approach is that we need to allocate two Vecs just to perform the operation.
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let mut x_as: Vec<&i32> = Vec::new();
let mut x_bs: Vec<&mut i32> = Vec::new();
for x in &mut xes {
x_as.push(&x.a);
x_bs.push(&mut x.b);
}
x_bs.iter_mut().for_each(|b| other(b, &x_as));
}
Here's an equivalent way of building the two Vecs using iterators:
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let (x_as, mut x_bs): (Vec<_>, Vec<_>) =
xes.iter_mut().map(|x| (&x.a, &mut x.b)).unzip();
x_bs.iter_mut().for_each(|b| other(b, &x_as));
}
Another way is to avoid mutable references completely and to use interior mutability instead. The standard library has Cell, which works well for Copy types such as i32, RefCell, which works for all types but does borrowing checking at runtime, adding some slight overhead, and Mutex and RwLock, which can be used in multiple threads but perform lock checks at runtime so at most one thread gets access to the inner value at any time.
Here's an example with Cell. We can eliminate the two temporary Vecs with this approach, and we can pass the whole collection of objects to the other function instead of just references to the a field.
use std::cell::Cell;
struct Test {
a: i32,
b: Cell<i32>,
}
fn other(x: &Cell<i32>, refs: &[Test]) {
x.set(x.get() + 1);
}
fn main() {
let xes: Vec<Test> = vec![Test { a: 3, b: Cell::new(5) }];
xes.iter().for_each(|x| other(&x.b, &xes));
}
I have the following code:
use std::collections::{HashMap, HashSet};
fn populate_connections(
start: i32,
num: i32,
conns: &mut HashMap<i32, HashSet<i32>>,
ancs: &mut HashSet<i32>,
) {
let mut orig_conns = conns.get_mut(&start).unwrap();
let pipes = conns.get(&num).unwrap();
for pipe in pipes.iter() {
if !ancs.contains(pipe) && !orig_conns.contains(pipe) {
ancs.insert(*pipe);
orig_conns.insert(*pipe);
populate_connections(start, num, conns, ancs);
}
}
}
fn main() {}
The logic is not very important, I'm trying to create a function which will itself and walk over pipes.
My issue is that this doesn't compile:
error[E0502]: cannot borrow `*conns` as immutable because it is also borrowed as mutable
--> src/main.rs:10:17
|
9 | let mut orig_conns = conns.get_mut(&start).unwrap();
| ----- mutable borrow occurs here
10 | let pipes = conns.get(&num).unwrap();
| ^^^^^ immutable borrow occurs here
...
19 | }
| - mutable borrow ends here
error[E0499]: cannot borrow `*conns` as mutable more than once at a time
--> src/main.rs:16:46
|
9 | let mut orig_conns = conns.get_mut(&start).unwrap();
| ----- first mutable borrow occurs here
...
16 | populate_connections(start, num, conns, ancs);
| ^^^^^ second mutable borrow occurs here
...
19 | }
| - first borrow ends here
I don't know how to make it work. At the beginning, I'm trying to get two HashSets stored in a HashMap (orig_conns and pipes).
Rust won't let me have both mutable and immutable variables at the same time. I'm confused a bit because this will be completely different objects but I guess if &start == &num, then I would have two different references to the same object (one mutable, one immutable).
Thats ok, but then how can I achieve this? I want to iterate over one HashSet and read and modify other one. Let's assume that they won't be the same HashSet.
If you can change your datatypes and your function signature, you can use a RefCell to create interior mutability:
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
fn populate_connections(
start: i32,
num: i32,
conns: &HashMap<i32, RefCell<HashSet<i32>>>,
ancs: &mut HashSet<i32>,
) {
let mut orig_conns = conns.get(&start).unwrap().borrow_mut();
let pipes = conns.get(&num).unwrap().borrow();
for pipe in pipes.iter() {
if !ancs.contains(pipe) && !orig_conns.contains(pipe) {
ancs.insert(*pipe);
orig_conns.insert(*pipe);
populate_connections(start, num, conns, ancs);
}
}
}
fn main() {}
Note that if start == num, the thread will panic because this is an attempt to have both mutable and immutable access to the same HashSet.
Safe alternatives to RefCell
Depending on your exact data and code needs, you can also use types like Cell or one of the atomics. These have lower memory overhead than a RefCell and only a small effect on codegen.
In multithreaded cases, you may wish to use a Mutex or RwLock.
Use hashbrown::HashMap
If you can switch to using hashbrown, you may be able to use a method like get_many_mut:
use hashbrown::HashMap; // 0.12.1
fn main() {
let mut map = HashMap::new();
map.insert(1, true);
map.insert(2, false);
dbg!(&map);
if let Some([a, b]) = map.get_many_mut([&1, &2]) {
std::mem::swap(a, b);
}
dbg!(&map);
}
As hashbrown is what powers the standard library hashmap, this is also available in nightly Rust as HashMap::get_many_mut.
Unsafe code
If you can guarantee that your two indices are different, you can use unsafe code and avoid interior mutability:
use std::collections::HashMap;
fn get_mut_pair<'a, K, V>(conns: &'a mut HashMap<K, V>, a: &K, b: &K) -> (&'a mut V, &'a mut V)
where
K: Eq + std::hash::Hash,
{
unsafe {
let a = conns.get_mut(a).unwrap() as *mut _;
let b = conns.get_mut(b).unwrap() as *mut _;
assert_ne!(a, b, "The two keys must not resolve to the same value");
(&mut *a, &mut *b)
}
}
fn main() {
let mut map = HashMap::new();
map.insert(1, true);
map.insert(2, false);
dbg!(&map);
let (a, b) = get_mut_pair(&mut map, &1, &2);
std::mem::swap(a, b);
dbg!(&map);
}
Similar code can be found in libraries like multi_mut.
This code tries to have an abundance of caution. An assertion enforces that the two values are distinct pointers before converting them back into mutable references and we explicitly add lifetimes to the returned variables.
You should understand the nuances of unsafe code before blindly using this solution. Notably, previous versions of this answer were incorrect. Thanks to #oberien for finding the unsoundness in the original implementation of this and proposing a fix. This playground demonstrates how purely safe Rust code could cause the old code to result in memory unsafety.
An enhanced version of this solution could accept an array of keys and return an array of values:
fn get_mut_pair<'a, K, V, const N: usize>(conns: &'a mut HashMap<K, V>, mut ks: [&K; N]) -> [&'a mut V; N]
It becomes more difficult to ensure that all the incoming keys are unique, however.
Note that this function doesn't attempt to solve the original problem, which is vastly more complex than verifying that two indices are disjoint. The original problem requires:
tracking three disjoint borrows, two of which are mutable and one that is immutable.
tracking the recursive call
must not modify the HashMap in any way which would cause resizing, which would invalidate any of the existing references from a previous level.
must not alias any of the references from a previous level.
Using something like RefCell is a much simpler way to ensure you do not trigger memory unsafety.
I have the following code:
use std::collections::{HashMap, HashSet};
fn populate_connections(
start: i32,
num: i32,
conns: &mut HashMap<i32, HashSet<i32>>,
ancs: &mut HashSet<i32>,
) {
let mut orig_conns = conns.get_mut(&start).unwrap();
let pipes = conns.get(&num).unwrap();
for pipe in pipes.iter() {
if !ancs.contains(pipe) && !orig_conns.contains(pipe) {
ancs.insert(*pipe);
orig_conns.insert(*pipe);
populate_connections(start, num, conns, ancs);
}
}
}
fn main() {}
The logic is not very important, I'm trying to create a function which will itself and walk over pipes.
My issue is that this doesn't compile:
error[E0502]: cannot borrow `*conns` as immutable because it is also borrowed as mutable
--> src/main.rs:10:17
|
9 | let mut orig_conns = conns.get_mut(&start).unwrap();
| ----- mutable borrow occurs here
10 | let pipes = conns.get(&num).unwrap();
| ^^^^^ immutable borrow occurs here
...
19 | }
| - mutable borrow ends here
error[E0499]: cannot borrow `*conns` as mutable more than once at a time
--> src/main.rs:16:46
|
9 | let mut orig_conns = conns.get_mut(&start).unwrap();
| ----- first mutable borrow occurs here
...
16 | populate_connections(start, num, conns, ancs);
| ^^^^^ second mutable borrow occurs here
...
19 | }
| - first borrow ends here
I don't know how to make it work. At the beginning, I'm trying to get two HashSets stored in a HashMap (orig_conns and pipes).
Rust won't let me have both mutable and immutable variables at the same time. I'm confused a bit because this will be completely different objects but I guess if &start == &num, then I would have two different references to the same object (one mutable, one immutable).
Thats ok, but then how can I achieve this? I want to iterate over one HashSet and read and modify other one. Let's assume that they won't be the same HashSet.
If you can change your datatypes and your function signature, you can use a RefCell to create interior mutability:
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
fn populate_connections(
start: i32,
num: i32,
conns: &HashMap<i32, RefCell<HashSet<i32>>>,
ancs: &mut HashSet<i32>,
) {
let mut orig_conns = conns.get(&start).unwrap().borrow_mut();
let pipes = conns.get(&num).unwrap().borrow();
for pipe in pipes.iter() {
if !ancs.contains(pipe) && !orig_conns.contains(pipe) {
ancs.insert(*pipe);
orig_conns.insert(*pipe);
populate_connections(start, num, conns, ancs);
}
}
}
fn main() {}
Note that if start == num, the thread will panic because this is an attempt to have both mutable and immutable access to the same HashSet.
Safe alternatives to RefCell
Depending on your exact data and code needs, you can also use types like Cell or one of the atomics. These have lower memory overhead than a RefCell and only a small effect on codegen.
In multithreaded cases, you may wish to use a Mutex or RwLock.
Use hashbrown::HashMap
If you can switch to using hashbrown, you may be able to use a method like get_many_mut:
use hashbrown::HashMap; // 0.12.1
fn main() {
let mut map = HashMap::new();
map.insert(1, true);
map.insert(2, false);
dbg!(&map);
if let Some([a, b]) = map.get_many_mut([&1, &2]) {
std::mem::swap(a, b);
}
dbg!(&map);
}
As hashbrown is what powers the standard library hashmap, this is also available in nightly Rust as HashMap::get_many_mut.
Unsafe code
If you can guarantee that your two indices are different, you can use unsafe code and avoid interior mutability:
use std::collections::HashMap;
fn get_mut_pair<'a, K, V>(conns: &'a mut HashMap<K, V>, a: &K, b: &K) -> (&'a mut V, &'a mut V)
where
K: Eq + std::hash::Hash,
{
unsafe {
let a = conns.get_mut(a).unwrap() as *mut _;
let b = conns.get_mut(b).unwrap() as *mut _;
assert_ne!(a, b, "The two keys must not resolve to the same value");
(&mut *a, &mut *b)
}
}
fn main() {
let mut map = HashMap::new();
map.insert(1, true);
map.insert(2, false);
dbg!(&map);
let (a, b) = get_mut_pair(&mut map, &1, &2);
std::mem::swap(a, b);
dbg!(&map);
}
Similar code can be found in libraries like multi_mut.
This code tries to have an abundance of caution. An assertion enforces that the two values are distinct pointers before converting them back into mutable references and we explicitly add lifetimes to the returned variables.
You should understand the nuances of unsafe code before blindly using this solution. Notably, previous versions of this answer were incorrect. Thanks to #oberien for finding the unsoundness in the original implementation of this and proposing a fix. This playground demonstrates how purely safe Rust code could cause the old code to result in memory unsafety.
An enhanced version of this solution could accept an array of keys and return an array of values:
fn get_mut_pair<'a, K, V, const N: usize>(conns: &'a mut HashMap<K, V>, mut ks: [&K; N]) -> [&'a mut V; N]
It becomes more difficult to ensure that all the incoming keys are unique, however.
Note that this function doesn't attempt to solve the original problem, which is vastly more complex than verifying that two indices are disjoint. The original problem requires:
tracking three disjoint borrows, two of which are mutable and one that is immutable.
tracking the recursive call
must not modify the HashMap in any way which would cause resizing, which would invalidate any of the existing references from a previous level.
must not alias any of the references from a previous level.
Using something like RefCell is a much simpler way to ensure you do not trigger memory unsafety.