I've stumbled over a lifetime issue when borrowing inside a loop.
After minification (keeping the original error message) it looks pretty useless by now. In the original I'm using the Splitter
instances to send data from the buffer
in packets of a given size.
Given certain circumstances I want to reuse the buffer, put new data into it and for that I create a new Splitter
.
I had thought that creating a new Splitter
instance would end the borrow, but it's still tracked across loop iterations.
struct Splitter<'a> {
slice: &'a [u8],
}
impl<'a> Splitter<'a> {
pub fn has_next(&self) -> bool {
todo!()
}
}
struct Builder<'a> {
pub buffer: &'a mut [u8],
}
impl<'a> Builder<'a> {
pub fn into_slice(self) -> &'a [u8] {
self.buffer
}
}
pub fn demo() -> ! {
let mut buffer = [0u8; 8];
let mut splitter_a = Splitter { slice: &[] };
let mut splitter_b = Splitter { slice: &[] };
loop {
// choose which buffer to send...
let _ = if splitter_a.has_next() {
&mut splitter_a
} else {
// changing this to `&mut splitter_a` will make the code compile?
&mut splitter_b
};
// explicitly overwriting splitter_a does not help
// splitter_a = Splitter { slice: &[] };
// neither does dropping (which would have been surprising thinking about
// it, since Splitter does not implement Drop so NLL should handle that?)
// drop(splitter_a);
let b = Builder {
buffer: &mut buffer,
};
//let slice = b.into_slice();
splitter_a = Splitter { slice: b.buffer };
}
}
Errors:
Compiling playground v0.0.1 (/playground)
error[E0499]: cannot borrow `buffer` as mutable more than once at a time
--> src/lib.rs:38:21
|
26 | let _ = if splitter_a.has_next() {
| ---------- first borrow used here, in later iteration of loop
...
38 | buffer: &mut buffer,
| ^^^^^^^^^^^ `buffer` was mutably borrowed here in the previous iteration of the loop
For more information about this error, try `rustc --explain E0499`.
error: could not compile `playground` (lib) due to 1 previous error
Am I correct in thinking that the issue here is that the lifetime of splitter_a
is until the end of the function, and the indermediate drop
that could happen in the loop is "ignored" by the borrow checker?
If yes, I'd like to understand why changing the second branch of the if to &mut splitter_a
let's this compile - I don't see how it makes the situation any better, considering any issues are bound to be linked to splitter_a
, and the _
should be dropped after the if?
And also why flat-out replacing splitter_a
does not help in this case.
I checked a few sources, but wasn't very successfull in finding a real answer - maybe this helps to see where I'm going wrong:
- The nomicon does not mention how loops are handled in detail
- This old thread: Fighting the borrow checker, in a loop seems similar, but confused me a bit because it seems from the responses that it should have been fixed with the 2018 edition, but actually doesn't compile currently?
- some descriptions on polonious, but those don't seem to apply since they all talk about early returns and named lifetimes.
- Blog on common lifetime misconceptions, this seems not to apply since the issue persists even if
b
is dropped before creating a newSplitter
(by commenting in the line withinto_slice
(can't post that link anymore, but you can find this here: hxxps://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md#9-downgrading-mut-refs-to-shared-refs-is-safe)
Edit: ignore the last point, with into_slice
it would make sense as @paramagnetic pointed out.