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.
-
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.
-
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 theoryConnection::update
could incorrectly useself.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, theService
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