How to satisfy the borrow checker in this example?

Hi everyone,

I am trying to get the following piece of code to compile, but I cannot. (It's a stripped down example, with types replaced for simpler ones where possible)

Basically I am transferring a mutable borrow into an enum, which I then deconstruct and either return the borrow, or throw it away. If I throw it away, I continue the loop and make a new borrow.

However, the borrow checker seems to disagree that the enum has "given up" on its borrow if the match is exited. Can anyone explain why that is?

// I have no influence over this enum
enum ForeignEnum<'a> {
    A(&'a i32),
    B(&'a i32),
}

// I have no influence over this function
fn foreign_code<'buffer>(b: &'buffer mut Vec<i32>) -> ForeignEnum<'buffer>  {
    if rand::random() {
        ForeignEnum::A(&b[0])
    } else {
        ForeignEnum::B(&b[1])
    }
}

// How to make this function compile?
fn external_borrow<'buffer>(b: &'buffer mut Vec<i32>) -> &'buffer i32 {
    loop {
        match foreign_code(b) {
            ForeignEnum::A(_) => {},
            ForeignEnum::B(b) => return b,
        }
    }
}

fn internal_borrow<'buffer>(b: &'buffer mut Vec<i32>) -> &'buffer i32 {
    loop {
        let owned = if rand::random() {
            ForeignEnum::A(&b[0])
        } else {
            ForeignEnum::B(&b[1])
        };

        match owned {
            ForeignEnum::A(_) => {},
            ForeignEnum::B(b) => return b,
        }
    }
}

(Playground)

Unfortunately, there is no way to write your external_borrow function. What might be confusing here is the lack of a reborrow. Consider this modified function:

fn external_borrow_copied<'buffer>(b: &'buffer mut Vec<i32>) -> i32 {
    loop {
        match foreign_code(b) {
            ForeignEnum::A(_) => {},
            ForeignEnum::B(b) => return *b,
        }
    }
}

It looks similar to external_borrow, but this version actually compiles. Here, when we call foreign_code(b), we aren't actually giving foreign_code the entire &'buffer mut Vec<i32>. Instead, we give it a shorter-lived borrow that lasts the length of the match. Effectively, the compiler implicitly calls foreign_code(&mut *b) to produce a shorter borrow; this is known as a reborrow.

But external_borrow specifically returns a &'buffer i32. Working backward, the compiler knows that we must have extracted it from a ForeignEnum<'buffer>, and that we must have gotten that from a &'buffer mut Vec<i32>. So it can't actually insert an implicit reborrow, since foreign_code requires that *b is borrowed for all of 'buffer. Therefore, as the error message says, once you call foreign_code(b) in one iteration, you can't access b in the next iteration.

1 Like

Thank you, I understand. I have adapted your solution now.

Also note that even though you have no control over the enum, in general it doesn't make much sense to have a field of type &i32. This is because it causes indirection, while on most modern systems an i32 is half the size of a thin pointer.
And in addition, it makes values of the enum harder to work with because of the generic lifetime parameter that now needs to be attached to the type.

Thanks, true! This is just example code in reality the i32 are some wrappers around [u8]. I stumbled upon this problem when trying to use the quick-xml crate.