"Locking" structure field for function duration

Hey,

so I have a use cases which almost make sense within Rust static analysis, but I'm feeling I'm missing one part to make it safe. So let's consider the following code:

pub struct Connection {
    stream: TcpStream,
}

impl Connection {
    pub fn update(&mut self, service: &mut Service) {
    }
}

pub struct Service {
    connections: HashMap<Token, Connection>,
}

impl Service {
    pub fn update(&mut self) {
        for connection in self.connections.values_mut() {
            connection.update(self);
        }
    }
}

So here it doesn't work, because there is two mutable borrow of self. Namely, when creating the iterator and when calling Connection::update. I perfectly understood and agree with the analysis of the compiler, but I'm not quite able to find what I need to make it work even though that I would imagine this pattern isn't unique to me.

Also I've seen different "solutions" online that I think didn't really address the issue.

  1. In one solution someone was using a vector, so it was recommended to use the index. But, imo that doesn't really address the problem, it simply mean that there is bound checking at every iteration making it safe. In the case of an HashMap, making it safe is not as cheap as bound checking.

  2. An other solution would be to move the value in a local variable. It's not possible possible in this case, because self.connections is behind a mutable reference and in theory Connection::update could incorrectly use self.connections.

So, it seem that what I need is a lock (not in the sense of thread) that ensure that I can only borrow once self.connections throughout the duration of Service::update, more generally for a scope. I thought RefCell would do the job, but it borrow the reference to self preventing me from then borrowing a mutable reference to self.

To add a bit more context why I want to pass the service to the update of connections.

  • Service owns connections (privately) which guarantee that when Connection::update is called, the Service is alive. It's effectively a parent.
  • Every connection may want to access concurrently some structure in Service. For instance, configuration, statistics aggregator, database connector, etc. All happen concurrently in the same thread ensuring no locks are required.

I guess a piece of code that I could have imagined would be something like:

impl Service {
    pub fn update(&mut self) {
        let connections = self.connections.lock();
        for connection in connections.values_mut() {
            connection.update(self);
        }

        // trying to call self.connections.lock() would panic! until we exit the function.
    }
}

This can be achieved with the following Lock structure, but has some issues. Wondering if some people already made this work, more thoroughly.

struct Lock<T> {
    inner: Option<T>,
}

impl<T> Lock<T> {
    fn new(val: T) -> Self {
        Self { inner: Some(val) }
    }

    fn take(&mut self) -> T {
        match self.inner.take() {
            Some(val) => val,
            None => panic!("Expected a value"),
        }
    }

    fn release(&mut self, val: T) {
        self.inner = Some(val)
    }
}

Anyhow, it seems that there could be a safe way to do that, but can't quite find how. Any ideas?

Thanks

As you've correctly surmised, the reason the compiler is rejecting your code is because you borrow a connection mutably (which comes from &mut self.connections, which comes from &mut self) but your update() method also requires &mut self.

The way I would resolve this issue is to not make update() a method on Service. Instead, pull it out into a free function and pass in just the fields you need.

impl Service {
    pub fn update(&mut self) {
        for connection in self.connections.values_mut() {
            update(connection, &self.some_field);
        }
    }
}

fn update(connection: &mut Connection, field: &mut String) { ... }
1 Like

This kind of “lock” would be Option, following/fixing the

approach:

pub struct Connection {
    stream: TcpStream,
}

impl Connection {
    pub fn update(&mut self, service: &mut Service) {
    }
}

pub struct Service {
    connections: Option<HashMap<Token, Connection>>,
}

impl Service {
    pub fn update(&mut self) {
        // "lock" == take
        let mut connections = self.connections.take().unwrap();
        for connection in connections.values_mut() {
            connection.update(self);
        }
        // "unlock" == put back
        self.connections = Some(connections);
    }
}

Edit: Should’ve perhaps read to the end of your post xD. Anyways, the benefit of an additional wrapper type around the Option seems small. In case your use case allows to statically verify its correctness, e.g. because Connection::update is known to never need/try to access service.connections, then the approach of splitting the struct up into multiple parts seems decent.

The reason I didn't went this way was because the number of field I might want to use is the factor that grows where the number of field that I want to "lock" is constant.

Similarly, one could have an approach where every fields but connections would have interior mutability.

Exactly what I was thinking. :stuck_out_tongue:

I even edited the answer to include the example. That said, do you know there was already work that does it and potentially that automatically reset the Option at the end of the scope?

No need to implement it, I can do it if I want it, but mostly wondering if it was already a concept that was standard.

That’s hard because you cannot easily retain any reference to the field that stores the correct location for where to put back the struct afterwards. I mean, it “might” work with raw pointers if your connections lives on the heap. But the ownership too… hmm. Then maybe - ah! - an Rc<RefCell<…>> could do the trick?

pub struct Connection {
    stream: TcpStream,
}

impl Connection {
    pub fn update(&mut self, service: &mut Service) {
    }
}

pub struct Service {
    connections: Rc<RefCell<HashMap<Token, Connection>>>,
}

impl Service {
    pub fn update(&mut self) {
        for connection in self.connections.clone().borrow_mut().values_mut() {
            connection.update(self);
        }
    }
}

Comment with hindsight: What problem cannot be solved with Rc<RefCell<T>>!? :sweat_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.