Search code examples
rustasync-awaitlifetimerust-tokio

Why `match` holds references longer than `if` in Rust?


Noticed the following difference between match and if in Rust regarding lifetimes - let's say we have simple struct:

struct Foo;

impl Foo {
  is_valid(&self) -> bool { true }
}

and a mutex that holds it:

let foo = tokio::sync::Mutex::new(Foo);

With the following match the lock exists until the end of the match construct:

match foo.lock().await.is_valid() {
  _ => foo.lock().await.is_valid(), // Deadlock.
};

This will not finish as the lock still holds when the second line is trying to acquire it. However using an if will release the first lock right after evaluating the bool:

if foo.lock().await.is_valid() {
  foo.lock().await.is_valid(); // This is fine.
}

This code finishes with the lock engaged twice. Does this mean match hangs on the references more than if? What's the explanation?


Solution

  • I believe this issue will give you the information you're looking for.

    In short the Drop trait is triggered after everything in the match statement is resolved. That means that by the time the inner lock is called, the outer one hasn't been freed yet. For the if statement, the Drop trait is called immediately after the condition is checked and before the condition block is called.

    Interestingly, this seems to also occur with if let expressions too. The following code also deadlocks:

    struct Foo;
    
    impl Foo {
        fn is_valid(&self) -> bool { true }
    }
    
    #[tokio::main]
    async fn main() {
        let foo = tokio::sync::Mutex::new(Foo);
    
        if let true = foo.lock().await.is_valid() {
            foo.lock().await.is_valid();
        };
    }