"Impossible" message from the borrow checker

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.

A version mismatch? Or are you using the code in different ways?

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.

And you are also using 1.77.0?

Yes.

If you start a fresh project and copy only this code into it, does it compile?

Yes. It also compiles as a standalone file with rustc.

Then it is the result of some interaction between your code and this, and we don't know your code.

The only thing I've thought of besides "we're missing some context" is to try cargo clean.

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.

Thanks! I'll look immediately into it.

If you wanna try the full thing, just

git clone git@github.com:vigna/webgraph-rs.git
cd webgraph-rs
cargo c --all-targets

Oh wow. So using a trait for which there is a bound triggers the behavior. Why ever?

In our code, however, the loop fails even if it is empty.

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

instead, then it compiles fine.

Also it’s not the bound that triggers the error but the println statement.

1 Like

BTW: no, the error appears even if the function is never called.

I can still reproduce using the original lender crate

use lender::*;

pub trait NodeLabelsLender<'lend, __ImplBound: ImplBound = Ref<'lend, Self>>:
    Lender + Lending<'lend, __ImplBound, Lend = (usize, Self::IntoIterator)>
{
    type Label;
    type IntoIterator: IntoIterator<Item = Self::Label>;
}


// Labelling trait. An Iterator lends Successors iterators.

pub trait SequentialLabelling {
    type Label;
    type Iterator<'node>: NodeLabelsLender<'node, Label = Self::Label>
    where
        Self: 'node;
    
    fn iter(&self) -> Self::Iterator<'_>;
}

pub trait SplitLabeling {
    type Lender<'a>: NodeLabelsLender<'a>
    where
        Self: 'a;
    type IntoIterator<'a>: IntoIterator<Item = Self::Lender<'a>>
    where
        Self: 'a;
    fn split_iter(&self, n: usize) -> Self::IntoIterator<'_>;
}

fn test_split_iter<S: SequentialLabelling + SplitLabeling>(g: &S) {
    let mut split_iter = g.split_iter(10).into_iter();
    let mut lender = split_iter.next().unwrap();

    while let Some(_) = lender.next() {
    
    }
}

fn main() {
    println!("2");    
}

And here it still is after I’ve copied the trait definitions from lender myself: Rust Playground

I haven’t yet searched for the difference.

1 Like

Looks like Lending was defined differently:

pub trait Lending<'lend, __ImplBound: ImplBound = Ref<'lend, Self>> {
    type Lend: 'lend;
}

vs

pub trait Lending<'lend, __Seal = &'lend Self> {
    type Lend;
}

Edit: For some reason it seems to be significant whether &'lend Self or Ref<'lend, Self> is used here

pub trait Lending<'lend, __ImplBound =
// &'lend Self // comment out one
Ref<'lend, Self> // of these lines
> {
    type Lend: 'lend;
}

Edit2: And it fails again if we make NodeLabelsLender use the same:

pub trait Lending<'lend, __ImplBound = &'lend Self> {
    type Lend: 'lend;
}

pub trait NodeLabelsLender<'lend, __ImplBound = &'lend Self>:
    Lender + Lending<'lend, __ImplBound, Lend = (usize, Self::IntoIterator)>
{
    type Label;
    type IntoIterator: IntoIterator<Item = Self::Label>;
}

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 didn't notice anything different. Did you change any other code?

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?

IDK. Yeah, somehow yes, I suppose. I still have zero clue why this is all significant.