Scope of "if let" variable

I have a code snippet like this:

pub struct A {
   collection: RwLock<HashMap<i32, ValueType>>
}

impl A {
  fn foo(&self) {
    if let Some(value) = self.collection.read().get(key) {
       // ...
    } else {
       let mut colleciton = self.collection.write();
       // ...
    }
  }
}

This code appears to deadlock. I was expecting the scope of the newly created variable "value" to exist only in the if block and not to continue into the else. It looks like the read lock is not released when the execution enters the else block and then it deadlocks trying to get the write lock while already holding the read lock.

Is this the expected behavior or am I doing something wrong here?

P.S: I have simplified the code quite a bit to describe my problem in very simple terms. I might have missed some nuances in the process. If needed I can elaborate closer to the exact code I have, which is a bit more complicated.

value exists only within the block. Your code deadlocks because the guard self.collection.read() has scope equal to the whole expression. This is so because an if let is basically syntax sugar for a match

match self.collection.read().get(key) {
    Some(value) => { ... }
    None => { let mut collection = self.collection.write(); ... }
}

The scrutinee of a match expression must live as long as the entire expression, because in general all variants may access its inner data, and there is no point in special-casing Option. A subexpression always lives as long as the entire expression (otherwise it would be impossible to borrow from a temporary borrowed value, like in your expresssion). This means that the lock guard lives for the entire match, and so your code deadlocks.

It's really "working as intended" here, but perhaps there could be a lint which warns against such deadlocks. It's not an uncommon error.

2 Likes

Thank you for the explanation. That makes sense. Yes, some kind of linter warning here would have helped a lot. It was very tricky to track down and I am worried about other hidden deadlocks lurking in the dark now.

The significant_drop_in_scrutinee lint tries to do this, but it had so many false positives that it was removed from the default-enabled set. So, you can enable it in your own code, but it'll need a contribution of a better algorithm to be enabled by default.

2 Likes