Rustc complains about borrowing multiple times when early return is used

I have the following code:

pub fn foo(bytes: &mut [u8]) -> (Option<&mut [u8]>, &mut [u8]) {
    if bytes.len() > 4 {
        let (head, tail) = bytes.split_at_mut(4);
        if head[0] != 0 {
            return (Some(head), tail);
        }
        // borrow ends here?
    }
    (None, bytes)
}

(playground)

here it seems that the parts created by split_at_mut live only until the end of the block, but compiler thinks there is a double-borrow on the last line of the function. At the same time this code compiles:

pub fn foo(bytes: &mut [u8]) -> (Option<&mut [u8]>, &mut [u8]) {
    if bytes.len() > 4 {
        let (head, tail) = bytes.split_at_mut(4);
        if head[0] != 0 {
            return (Some(head), tail);
        } else {
            return (None, tail); // works
        }
    }
    (None, bytes)
}

Why compiler doesn't understand that the borrow lives until the end of the block and not until the end of the function? Are there ways to overcome this limitation?

Looks like NLL problem case #3 -- conditional return from a function. The current compiler forces the borrow to extend throughout the rest of the function body.

There are some workaround in the documentation here (or you can use the crate itself).

Your code is probably minimized beyond what you actually need, but this works for example.

    if bytes.len() <= 4 || bytes[0] == 0 {
        (None, bytes)
    } else {
        let (head, tail) = bytes.split_at_mut(4);
        (Some(head), tail)
    }
1 Like

There is also still ongoing work to enable the next-generation borrow checker “Polonius” (mentioned in the links above) by default, so future versions of rustc should be able to compile your code without problems.

2 Likes

That doesn't remove the head when bytes[0] == 0. Still compiles when fixed though.

pub fn foo(bytes: &mut [u8]) -> (Option<&mut [u8]>, &mut [u8]) {
    if bytes.len() <= 4 {
        return (None, bytes);
    }
    
    let (head, tail) = bytes.split_at_mut(4);
    if head[0] != 0 {
        (Some(head), tail)
    } else {
        (None, tail)
    }
}

Ah now I see there were differently behaving snippets posted, heh.

Oh, yeah I didn't notice that either. Yours is probably what they wanted.

Do you suppose that a Polonius integration as default for rustc would also be the versioned release of rust-2.0?

Polonius isn't intended to be a major breaking change.

AFAIK it's still the goal of the teams to never have a Rust 2.0.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.