This playground shows the shape of the code I'm writing. It compiles fine when the _nontrivial_drop field of Encoder is commented out. However, when that field is added, we get some errors from the drop check (I think):
error[E0499]: cannot borrow `*stuff` as mutable more than once at a time
--> src/lib.rs:58:57
|
51 | stuff,
| ----- first mutable borrow occurs here
...
56 | encoder = match encoder.block() {
| ------- first borrow might be used here, when `encoder` is dropped and runs the destructor for type `Encoder<'_>`
57 | Either::Left(enc) => break enc,
58 | Either::Right(save) => save.resume(grow(stuff)),
| ^^^^^ second mutable borrow occurs here
error[E0499]: cannot borrow `*stuff` as mutable more than once at a time
--> src/lib.rs:64:57
|
51 | stuff,
| ----- first mutable borrow occurs here
...
61 | encoder = loop {
| ------- first borrow might be used here, when `encoder` is dropped and runs the destructor for type `Encoder<'_>`
...
64 | Either::Right(save) => save.resume(grow(stuff)),
| ^^^^^ second mutable borrow occurs here
For more information about this error, try `rustc --explain E0499`.
Is there actually a soundness issue here? I wouldn't think so, since the drop glue for Encoder<'a> won't touch the &'a mut [u8] field. And if my code is sound, how can I (safely or unsafely) work around the drop check in this case? (I don't think the eyepatch helps.)
It’s not pretty or particularly DRY, but this compiles even though the control flow is essentially “the same”; probably because it successfully avoids mutating encoder or block_encoder in any loop that also involves repeated .resume(grow(stuff)) calls:
Might need block_encoder wrapped similarly if that one has nontrivial drop, too.
There isn’t even any downsides such as leak on panic, since the encoder is always immediately consumed with …::into_inner without anything that can panic in-between. I think. It's probably easy to restructure the code in order to make this even more obvious.
(Looking over my code again I'm struck by the bad/confusing type and variable names. Sorry about that.)
Thanks, this seems promising. I'll report back whether I'm able to apply it to my real code.
I need to study this a bit more before I can wrap my head around it. It seems like the key is to temporarily split the nontrivial-Drop part of Encoder from the &mut part, so the compiler can't see problematic interaction between them.
Looks familiar. I guess I misidentified the issue in my OP—the destructor of Encoder<'a> never runs at all, because the value is consumed by Encoder::block instead, but the compiler doesn't see that for whatever reason.
Neat! I'd love to see whether the core logic here can be packaged up into a utility method on Encoder or something, because ultimately I want consumers of my library to be able to write this kind of loop in their own code.