Strangeness in pattern matching mutable references in nested structs

There is a stackoverflow question that asks what the difference between matching with let Some(next) = &mut node.next and let Some(ref mut next) = node.next is.

I looked into binding modes and move/binding semantics of patterns but for life of my I can not figure out why one of these functions compiles but the other doesn't.

struct Node {
    next: Option<Box<Node>>,
}

fn node_matched<'a>(mut node: &'a mut Node)  {
    if let Some(next) = &mut node.next {
        node = next;
    }
    // here the compiler thinks node.next is still borrowed
    node.next = None;
}

fn node_reffed<'a>(mut node: &'a mut Node) {
    if let Some(ref mut next) = node.next {
        node = next;
    }
    // here the compiler thinks node.next is not borrowed anymore
    node.next = None;
}

I have been experimenting with different patterns and scenarios to figure this out and this only seems to happen with mutable references but I might be wrong.. Maybe its a bug maybe it is just some interested mechanism that isn't very well known but after spending quite some time trying to answer that stackoverflow question I really really itch to know what is going on.

1 Like

There's another bug report related to this, similar to the one linked in that StackOverflow thread but with a bit more discussion: if/while Some(n) = &mut foo sugar will leak a temporary mutable borrow to current scope in particular situation · Issue #62013 · rust-lang/rust · GitHub

I didn't follow all of the details, but I believe the difference in behavior here is unintentional and will be fixed eventually. When compiling with the next-generation borrow checker Polonius, both functions compile successfully.

3 Likes

Isn't the first example a mutable borrow of an Option, while the second is a mutable reference to a value inside an option? It kind of makes sense to me the compiler would think the first borrow was still alive.

2 Likes