0

The question says "a collection in a struct", but I'm uncertain whether the "in a struct" part is actually relevant. I may not understand the problem well enough to properly ask the question. In any case, I'm trying to do the following:

Create a graph. Each node in the graph has a collection of string identifiers indicating which other nodes it is connected to. Of note: This collection also includes nodes which it is implicitly connected to via other nodes, which it distinguishes by indicating their distance (implicit connections will have distance > 1). Here's the code:

use core::mem::MaybeUninit;
use core::ptr;
use std::collections::HashMap;
use std::sync::Once;

// In my actual code, this map is global for WebAssembly reasons.
// I'm leaving it global to avoid introducing new borrow-checker issues on top of my existing ones.
// Please roll with it. I realize there may be better implementations.
static mut NODE_MAP_UNSAFE: MaybeUninit<HashMap<String, Node>> = MaybeUninit::uninit();
static NODE_MAP_INIT: Once = Once::new();

// For readability
macro_rules! node_map { () => { unsafe { &(*NODE_MAP_UNSAFE.as_ptr()) } }; }
macro_rules! node_map_mut { () => { unsafe { &mut (*NODE_MAP_UNSAFE.as_mut_ptr()) } }; }

// Contains the node's identifier and its connections
struct Node {
    name: String,
    connections: HashMap<String, Connect>,
}

// Contains the identifier of the node we're connected to, as well as its distance from us
struct Connect {
    name: String,
    dist: i32,
}

fn main() {
    // Again, please roll with it
    NODE_MAP_INIT.call_once(|| unsafe { ptr::write(NODE_MAP_UNSAFE.as_mut_ptr(), HashMap::new()); });

    // Create nodes 1-3 and add them to the map
    let node1 = Node::new("1");
    let node2 = Node::new("2");
    let node3 = Node::new("3");
    node_map_mut!().insert(String::from("1"), node1);
    node_map_mut!().insert(String::from("2"), node2);
    node_map_mut!().insert(String::from("3"), node3);

    // Note: I will be skipping all Option::None checks for brevity's sake.
    // The actual code would of course perform the checks properly.

    // Connect node 1 to node 2, and vice versa
    node_map_mut!().get_mut(&String::from("1")).unwrap().add_connection(String::from("2"));
    node_map_mut!().get_mut(&String::from("2")).unwrap().add_connection(String::from("1"));

    // Connect node 2 to node 3, and vice versa
    node_map_mut!().get_mut(&String::from("2")).unwrap().add_connection(String::from("3"));
    node_map_mut!().get_mut(&String::from("3")).unwrap().add_connection(String::from("2"));

    // Implicitly connect node 1 to node 3, via node 2
    node_map_mut!().get_mut(&String::from("1")).unwrap().update_connections(String::from("2"));
}

impl Node {
    fn new(name: &str) -> Node {
        return Node {
            name: String::from(name),
            connections: HashMap::new(),
        }
    }

    // Add a connection
    fn add_connection(&mut self, comp_to_add: String) {
        // Create the new connection
        let new_connection = Connect { name: comp_to_add.clone(), dist: 1 };

        // Implicitly connect to distant nodes via our new connection
        // No problem here, since the connection doesn't belong to self.connections yet
        self.infer(&node_map!().get(&new_connection.name).unwrap().connections);

        // Add the connection to self.connections
        self.connections.insert(comp_to_add.clone(), new_connection);
    }

    // Check for new implicit connections and create them, if they exist.
    fn update_connections(&mut self, comp_to_update_via: String) {
        // Retrieve the existing connection from self.connections (thus borrowing it).
        let existing_connection = &self.connections.get(&comp_to_update_via).unwrap().name;

        // Implicitly connect to distant nodes via the existing connection
        self.infer(&node_map!().get(existing_connection).unwrap().connections); // problem here
    }

    fn infer(&mut self, map: &HashMap<String, Connect>) {
        for element in map {
            // Ignore connections to us
            if element.0 == &self.name { continue; }

            // Add the implicit connection to self.connections (thus borrowing it).
            // Actual code would instead update the connection if it already existed.
            self.connections.insert(element.0.clone(), Connect { name: element.0.clone(), dist: element.1.dist + 1 });
        }
    }
}

This gives the following warning:

warning: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/main.rs:76:9
   |
73 |         let existing_connection = self.connections.get(&comp_to_update_via).unwrap();
   |                                   ---------------- immutable borrow occurs here
...
76 |         self.infer(&node_map!().get(&existing_connection.name).unwrap().connections); // problem here
   |         ^^^^                        ------------------------- 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>

In my actual code, that's an error, but in this abbreviated version it's a warning for reasons I don't understand (maybe the error is being optimized out?). Either way, the problem is the same: self.connections is being borrowed to retrieve the name, and then self.connections is modified before the borrow ends. I could fix this in either of the following ways:

By cloning the String:

let existing_connection = self.connections.get(&comp_to_update_via).unwrap().name.clone();
self.infer(&node_map!().get(&existing_connection).unwrap().connections);

By doing the entire borrow inline:

self.infer(&node_map!().get(&self.connections.get(&comp_to_update_via).unwrap().name).unwrap().connections);

I dislike option 1 because throwing .clone()s around seems like a bad habit to be in. If the type in question were something more complex than a String, the performance cost could potentially be quite high.

I dislike option 2 because 1: it's ugly and hard to read, and 2: it wouldn't be possible (I don't think) if I were properly checking for Option::None. Also, I don't even understand why it works in the first place.

  • See also [Situations where Cell or RefCell is the best choice](https://stackoverflow.com/q/30831037/155423) – Shepmaster May 06 '20 at 17:03
  • 1
    Also take some time to read a selection of the [**150 existing questions**](https://stackoverflow.com/search?q=%5Brust%5D+is%3Aq+cannot+borrow+%60*self%60+as+mutable+because+it+is+also+borrowed+as+immutable) about the same thing. – Shepmaster May 06 '20 at 17:04
  • Thanks. These didn't solve my problem, but they did help organize my thinking. I'll open a new, better question. – VeryCasual May 07 '20 at 18:28

0 Answers0