Weird behavior of borrow checker

I have noticed an quite weird behavior of borrow checker that forbids to make two deconstruction attempts to some mutable reference to slice with else if let. This is forbidden by compiler even if slice is deferred mutable (&mut *slice), so in a way that does not move mutable reference.

if let &mut [.., item, ref mut reference] = &mut slice[..=index] {
    (item, reference)
} else if let &mut [ref mut reference, .., item] = slice {
    (item, reference)
} else {
    panic!();
}

Example in Rust Playground

Is that an expected behavior of compiler or an unexpected bug?

This is "NLL problem case #3", a known issue with the borrow checker — possibly the most notorious one that is actually about the borrow checking algorithm itself and not the type system of references. The borrow checker does not properly understand conditionals that either return a borrow or release it and take another borrow.

It will be fixed by the next-generation borrow checker algorithm, Polonius. Until then, the workaround is that you must not take the borrow that you intend to return, until you know that you will definitely return it, rather than dropping it.

10 Likes

Thank you very much, you clarified this case and helped me to sustain my believe in my good understanding of Rust's syntax. I will use proposed workaround.

In the particular case of pattern matching, using ref muts can actually be part of a workaround, because they count as taking the reference after the pattern successfully matches. For example, this modification to your program (removing the slicing operation on the right side) compiles:

    if let &mut [.., item, ref mut reference] = slice {
        (item, reference)
    } else if let &mut [ref mut reference, .., item] = slice {
        (item, reference)
    } else {
        panic!();
    }

However, this of course does not do what you want. You will probably have to compare slice.len() against index, instead.

5 Likes