Get a trait object reference from a vector

2019-07-08 08:03发布

问题:

I'm struggling to get an element from a Vec that is a reference with a lifetime. I've simplified my code down to this:

pub trait Runnable {}

pub struct RunList<'a> {
    runnables: Vec<&'a mut Runnable>,
}

impl<'a> RunList<'a> {
    pub fn get(&self, id: usize) -> &'a mut Runnable {
        self.runnables[id]
    }
}

fn main() {}

playground

which produces this error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
 --> src/main.rs:9:9
  |
9 |         self.runnables[id]
  |         ^^^^^^^^^^^^^^^^^^
  |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 8:5...
 --> src/main.rs:8:5
  |
8 | /     pub fn get(&self, id: usize) -> &'a mut Runnable {
9 | |         self.runnables[id]
10| |     }
  | |_____^
note: ...so that reference does not outlive borrowed content
 --> src/main.rs:9:9
  |
9 |         self.runnables[id]
  |         ^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 7:1...
 --> src/main.rs:7:1
  |
7 | impl<'a> RunList<'a> {
  | ^^^^^^^^^^^^^^^^^^^^
  = note: ...so that the expression is assignable:
          expected &'a mut Runnable + 'a
             found &mut Runnable + 'a

回答1:

First, remove the lifetime parameter 'a from the type of the return value of get:

pub trait Runnable {}

pub struct RunList<'a> {
    runnables: Vec<&'a mut Runnable>,
}

impl<'a> RunList<'a> {
    pub fn get(&mut self, id: usize) -> &mut Runnable {
        self.runnables[id]
    }
}

fn main() {}

This gets you to the underlying problem:

error[E0389]: cannot borrow data mutably in a `&` reference
 --> src/main.rs:9:9
  |
9 |         self.runnables[id]
  |         ^^^^^^^^^^^^^^^^^^ assignment into an immutable reference

self.runnables[id] borrows &'a mut Runnable immutably and dereferences it implicitly. You can't move &'a mut Runnable out of this immutable borrow. Instead, you can mutably reborrow the runnable which requires a mutable borrow of self:

impl<'a> RunList<'a> {
    pub fn get(&mut self, id: usize) -> &mut Runnable {
        &mut *self.runnables[id]
    }
}

To understand what's exactly going on here one should read up on: What is the return type of the indexing operation?

pub fn get(&mut self, id: usize) -> &mut Runnable {
    self.runnables[id]
}

is equivalent to:

pub fn get(&mut self, id: usize) -> &mut Runnable {
    let r: &&mut (Runnable + 'a)  = self.runnables.index(id);
    *r
}

index returns an immutable reference to &'a mut Runnable, which is of type &&mut (Runnable + 'a). Returning *r does not work, because you can't move the mutable reference1 out of the borrowed content and also data in an immutable reference can't be borrowed mutably (&mut**r does not work). Therefore, you need the version of index which grants mutable access to the indexed element: index_mut

pub fn get(&mut self, id: usize) -> &mut Runnable {
    let r: &mut &'a mut (Runnable + 'a)  = self.runnables.index_mut(id);
    *r
}

This works, because *r actually reborrows the Runnable inside mutably (&mut**r). In contrast to the case with the immutable reference r above, mutably borrowing the data inside the mutable reference r is perfectly fine. When performing &mut**r the parameter 'a gets lost in the process of dereferencing. So, returning &'a mut Runnable instead of &mut Runnable does not work here.

Finally, &mut *self.runnables[id] tells the compiler to implicitly resolve to *self.runnables.index_mut(id) instead of *self.runnables.index(id). *&mut self.runnables[id] would also work.


1 Mutable references are not Copy. If you changed all &'a mut Runnable in your code to &'a Runnable, it would compile, because immutable references are Copy



标签: rust