Rust struct can borrow "&'a mut self" twice, so why can't a trait?
Asked Answered
G

4

23

The following Rust code compiles successfully:

struct StructNothing;

impl<'a> StructNothing {
    fn nothing(&'a mut self) -> () {}

    fn twice_nothing(&'a mut self) -> () {
        self.nothing();
        self.nothing();
    }
}

However, if we try to package it in a trait, it fails:

pub trait TraitNothing<'a> {
    fn nothing(&'a mut self) -> () {}

    fn twice_nothing(&'a mut self) -> () {
        self.nothing();
        self.nothing();
    }
}

This gives us:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
 --> src/lib.rs:6:9
  |
1 | pub trait TraitNothing<'a> {
  |                        -- lifetime `'a` defined here
...
5 |         self.nothing();
  |         --------------
  |         |
  |         first mutable borrow occurs here
  |         argument requires that `*self` is borrowed for `'a`
6 |         self.nothing();
  |         ^^^^ second mutable borrow occurs here
  • Why is the first version allowed, but the second version forbidden?
  • Is there any way to convince the compiler that the second version is OK?

Background and motivation

Libraries like rust-csv would like to support streaming, zero-copy parsing because it's 25 to 50 times faster than allocating memory (according to benchmarks). But Rust's built-in Iterator trait can't be used for this, because there's no way to implement collect(). The goal is to define a StreamingIterator trait which can be shared by rust-csv and several similar libraries, but every attempt to implement it so far has run into the problem above.

Glomeration answered 4/10, 2014 at 11:55 Comment(4)
Changing fn nothing(&'a mut self) to fn nothing(&mut self) fixes the issue. Do you really need this lifetime specifier given your function returns nothing ? Yet, it does look like a bug.Impatiens
Levans: Yeah, without that lifetime specifier, the rest of the design falls apart. But if we could get that lifetime specifier to work, we could build a pretty good StreamingIterator library. This is just the minimal example that's left us scratching our heads.Glomeration
I believe this would be fixed with something like HRL (higher rank lifetimes), where you could have (hypothetical syntax) trait StreamingIterator<T<'*>> { fn next<'a>(&'a mut self) -> T<'a>; }. We don't have the ability to express exactly that now, though.Hussein
(It was pointed out that I misused terminology: the above should say "HKL (higher kinded lifetimes)".)Hussein
M
3

The following is an extension of Francis's answer using implicit lifetimes but it allows for the return value to be lifetime bound:

pub trait TraitNothing<'a> {
    fn change_it(&mut self);

    fn nothing(&mut self) -> &Self {
        self.change_it();
        self
    }

    fn bounded_nothing(&'a mut self) -> &'a Self {
        self.nothing()
    }

    fn twice_nothing(&'a mut self) -> &'a Self {
        // uncomment to show old fail
        // self.bounded_nothing();
        // self.bounded_nothing()
        self.nothing();
        self.nothing()
    }
}

It's less than perfect, but you can call the methods with implicit lifetimes change_it and nothing multiple times within other methods. I don't know if this will solve your real problem because ultimately self has the generic type &mut Self in the trait methods whereas in the struct it has type &mut StructNothing and the compiler can't guarantee that Self doesn't contain a reference. This workaround does solve the code example.

Mouse answered 10/2, 2015 at 2:15 Comment(0)
G
2

If you put the lifetime parameters on each method rather than on the trait itself, it compiles:

pub trait TraitNothing {
    fn nothing<'a>(&'a mut self) -> () {}

    fn twice_nothing<'a>(&'a mut self) -> () {
        self.nothing();
        self.nothing();
    }
}
Gaither answered 4/10, 2014 at 16:55 Comment(2)
Unfortunately, this does not allow the return value to be bounded by 'a, as is required for the real problem @Glomeration is trying to solve.Hussein
I knew there was a gotcha. But why, exactly? Also, I just tried putting <'a> on the trait and <'b: 'a> on the methods, but I just get back to the original problem. What does <'a> on a trait actually mean?Heteronomy
S
1

Nobody seemed to answer the "why?" so here I am.

Here's the point: In the trait, we're calling methods from the same trait. However, in the free impl, we're not calling methods from the same impl.

What? Surely we call methods from the same impl?

Let's be more precise: we're calling methods from the same impl, but not with the same generic parameters.

Your free impl is essentially equivalent to the following:

impl StructNothing {
    fn nothing<'a>(&'a mut self) {}

    fn twice_nothing<'a>(&'a mut self) {
        self.nothing();
        self.nothing();
    }
}

Because the impl's generic lifetime is floating, it can be chosen separately for each method. The compiler does not call <Self<'a>>::nothing(self), but rather it calls <Self<'some_shorter_lifetime>>::nothing(&mut *self).

With the trait, on the other hand, the situation is completely different. The only thing we can know for sure is that Self: Trait<'b>. We cannot call nothing() with a shorter lifetime, because maybe Self doesn't implement Trait with the shorter lifetime. Therefore, we are forced to call <Self as Trait<'a>>::nothing(self), and the result is that we're borrowing for overlapping regions.

From this we can infer that if we tell the compiler that Self implements Trait for any lifetime it will work:

fn twice_nothing(&'a mut self)
where
    Self: for<'b> TraitNothing<'b>,
{
    (&mut *self).nothing();
    (&mut *self).nothing();
}

...except it fails to compile because of issue #84435, so I don't know whether this would have succeeded :(

Steeplechase answered 11/1, 2023 at 17:4 Comment(0)
C
-4

Is this really surprising?

The assertion you're making is that &mut self lasts for at least the lifetime 'a.

In the former case, &mut self is a pointer to a struct. No pointer aliasing occurs because the borrow is entirely contained in nothing().

In the latter case, the &mut self is a pointer to a pointer to a struct + a vtable for the trait. You're locking the pointed to struct that implements TraitNothing for the duration of 'a; i.e. the whole function each time.

By removing 'a, you're implicitly using 'static, which says the impl lasts forever, so its fine.

If you want to work around it, transmute the &'a TraitNothing to &'static TraitNothing... but I'm pretty sure that's not what you want to do.

This is why we need block scopes ('b: { .... }) in Rust...

Try using dummy lifetimes perhaps?

Citarella answered 4/10, 2014 at 15:55 Comment(3)
The &mut self in the trait declaration is not a trait object; it is just a plain pointer to whatever value implements that trait. (Even if it was a &mut TraitNothing trait object, it would just be a pointer to a struct + a pointer to the vtable, without the extra layer of indirection.)Hussein
@dbaupp oh really? I thought trait &self implementations were done via something like is.gd/Gl20OQSherrer
@Doug, no, they're not; a default implementation for a method is the same as just writing that code in each impl, including being statically dispatched.Hussein

© 2022 - 2024 — McMap. All rights reserved.