23
votes

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.

3
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. - Levans
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. - emk
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. - huon
(It was pointed out that I misused terminology: the above should say "HKL (higher kinded lifetimes)".) - huon

3 Answers

3
votes

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.

2
votes

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();
    }
}
-4
votes

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?