Once again, we have some weird problems with a lender-based design. Our current designed was suggested by @quinedot and it is based on a sophisticated propagation of implicit trait bounds.
What we are facing now is a kind of "impossible" message from the borrow checker: the code of this playground compiles without problems. It contains the basic traits, and a SplitLabeling trait that makes it possible to split a lender in several pieces. The function test_split_iter iterates on the first piece. Everything compiles fine.
However, the same exact code, embedded in the rest of our codebase, gives the following error:
error[E0499]: cannot borrow `lender` as mutable more than once at a time
|
| while let Some(_) = lender.next() {
| ^^^^^^
| |
| `lender` was mutably borrowed here in the previous iteration of the loop
| first borrow used here, in later iteration of loop
This is the iteration in test_split_iter in the playground. It is a totally standard iteration, and in fact it compiles happily in the playground.
There must be some unbelievably weird interation with other traits, we guess, or, God forbid, a compiler bug.
If this rings any kind of bell, please tell us. We are really running out of ideas...
PS: We are compiling with Rust 1.77 and we tried the current nightly.
No, the playground uses 1.77. And there is no "usage" here—no code is running or is actually called, and the same happens in our code presently. The function won't just compile with that error.
I haven't played with it yet, but here's enough additions to trigger the error.
Perhaps your project has enough other additions, or perhaps in your project it's a post-monomorphization error (i.e. one that only triggers when you try to call the function).
Edit: The phrasing of that bound I added is what causes the error I guess, so it may or may not reflect what's going on in your project.
As far as I can tell, (without having tried to understand the code yet), it looks like the error in @quinedot's example relates to two lifetimes being equated.
for<'a> <<S as SplitLabeling>::Lender<'a> as Lending<'a>>::Lend: std::fmt::Debug
// ^^ ^^
// +--------------+--- these two are the same
If it’s
for<'a, 'b> <<S as SplitLabeling>::Lender<'a> as Lending<'b>>::Lend: std::fmt::Debug
so the significant difference was just whether both places were using the same construct with Ref<'lend, Self> or different ones Ref<'lend, Self> vs &'lend Self… for some reason.
I see. We introduced the Ref thing to make more "difficult" for the user putting in the implicit bound something wrong. When I put together the playground, for simplicity I used the playground from the discussion about the design of NodeLabelsLender, forgetting that in the meantime we had made that change.
So do you mean that when the two implicit bounds use a different type it works?