Mutable state in loop rejected only when using associated error type

I've encountered a problem, which I believe boils down to the following:

trait Foo<I> {
    type Error: std::error::Error;

    fn foo(&mut self, input: I) -> Result<(), Self::Error>;
}

trait Bar<I>: Foo<I> {
    fn loop_foo(&mut self, input: I) -> Result<(), Self::Error>;
}

impl<'a, I, T> Bar<&'a mut I> for T
where
    for<'b> T: Foo<&'b mut I>,
{
    fn loop_foo(&mut self, input: &'a mut I) -> Result<(), Self::Error> {
        loop {
            self.foo(input)?;

            # This some meaningful check, but the details do not matter here
            if true {
                break Ok(());
            }
        }
    }
}

(Playground)

The compiler rejects the above snippet with the following error message:

error[E0499]: cannot borrow `*input` as mutable more than once at a time
  --> src/lib.rs:17:22
   |
11 | impl<'a, I, T> Bar<&'a mut I> for T
   |      -- lifetime `'a` defined here
...
17 |             self.foo(input)?;
   |             ---------^^^^^--
   |             |        |
   |             |        `*input` was mutably borrowed here in the previous iteration of the loop
   |             returning this value requires that `*input` is borrowed for `'a`

For more information about this error, try `rustc --explain E0499`.

At first, I thought maybe this was some limitation on the borrow checker with mutable state in loop since there's stuff with HRTB going on. I seem to recall reading somewhere that certain things like this are blocked on Polonius.

However, I noticed that if I replace the associated type Error with the concrete error type, say, std::io::Error, in the signatures of foo and loop_foo, then this compiles fine:

trait Foo2<I> {
    fn foo(&mut self, input: I) -> Result<(), std::io::Error>;
}

trait Bar2<I>: Foo2<I> {
    fn loop_foo(&mut self, input: I) -> Result<(), std::io::Error>;
}

impl<'a, I, T> Bar2<&'a mut I> for T
where
    for<'b> T: Foo2<&'b mut I>,
{
    fn loop_foo(&mut self, input: &'a mut I) -> Result<(), std::io::Error> {
        loop {
            self.foo(input)?;

            if true {
                break Ok(());
            }
        }
    }
}

(Playground)

That made me think that I could fix the original version by providing some bound either on the associated Error type, or as a HRTB on the impl for T. I tried various ways of adding Copy and/or 'static to the error type, but I haven't found a solution. In my actual use case, I can get away with just using a concrete error type, but now I'm curious: Why does the first version not work, and is there a trait bound that fixes it?

The problem relatively straightforward.

If we write a type synonym for the associated type in question

type ErrorType<SelfType, I> = <SelfType as Foo<I>>::Error;

it becomes more obvious: the Error type depends on I.


Let’s explore what effects this dependency on I has.

Writing this synonym into the signatures is straightforward enough…

trait Foo<I> {
    type Error: std::error::Error;

    fn foo(&mut self, input: I) -> Result<(), ErrorType<Self, I>>;
}

trait Bar<I>: Foo<I> {
    fn loop_foo(&mut self, input: I) -> Result<(), ErrorType<Self, I>>;
}

but now in the problematic impl block… what do we write?

impl<'a, I, T> Bar<&'a mut I> for T
where
    for<'b> T: Foo<&'b mut I>,
{
    fn loop_foo(
        &mut self,
        input: &'a mut I,
    ) -> Result<(), ErrorType<Self, /* WHAT GOES HERE? */>>
    {
        loop {
            self.foo(input)?;

            if true {
                break Ok(());
            }
        }
    }
}

it’s not I, because I is something else in this context… how does the compiler even figure this out? Slightly nontrivial question, but to avoid the details on that, we can simply follow the principle that the type signature must match the one in the trait definition, i.e.

trait Bar<I>: Foo<I> {
    fn loop_foo(&mut self, input: I) -> Result<(), ErrorType<Self, I>>;
}

since the impl block is for Bar<&'a mut I>, this means, the gap has to be filled with &'a mut I, i.e.

impl<'a, I, T> Bar<&'a mut I> for T
where
    for<'b> T: Foo<&'b mut I>,
{
    fn loop_foo(
        &mut self,
        input: &'a mut I,
    ) -> Result<(), ErrorType<Self, &'a mut I>>
    {
        loop {
            self.foo(input)?;

            if true {
                break Ok(());
            }
        }
    }
}

Maybe that’s enough for you to fully understand the problem already, at least for me, with this clarification of the type signature, the error message suddenly makes a lot more sense. We return a Result<_, … &'a mut I …> and call foo which has (as part of the for<'b> T: Foo<&'b mut I> bound) a signature of the form for<'b> fn(&mut …, input: &'b mut I) -> Result<_, … &'b mut I …>, so of course, returning this Result’s error via the ? operator means that *input needs to be borrowed for 'a, otherwise the types wouldn’t match!

It’s also easy to see why none of this applies for the case of using std::io::Error, since that type does not depend on anything, in particular not on the trait parameter I.

2 Likes

A way to tell the compiler “hey, this type should not depend on I” would be to give Foo a non-generic supertrait, and put the associated type there. Rust Playground

Another approach could be to restrict the generic implementation to only the case where the Error type doesn’t depend on the lifetime. Rust Playground

2 Likes

Ah, that makes a lot of sense. Great explanation, thank you so much!

Hadn't occurred to me that Error type depends on I in the eyes of the compiler, but the type alias makes the point well.

Thanks again.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.