Corvus
Corvus

Reputation: 47

How to store a mutable borrow with a specified lifetime in an enum?

I want to be able to store a mutable borrow &mut Foo<'a> inside an enum variant. I can work with the mutable borrow directly just fine, but not with an enum containing the mutable borrow. Extract of simplified code below ('full' playground version with 'broken' code commented out here).

#[derive(Debug)]
struct Foo<'a> {
    shared_ref: &'a usize,
    child_foos: HashMap<usize, Foo<'a>>,
}
impl<'a> Foo<'a> {
    
    // Get a 'naked' mutable reference to the child foo - works fine!
    pub fn get_handle(&mut self, id: usize) -> &mut Foo<'a> {
        self.child_foos.get_mut(&id).unwrap()
    }

    // !!! Get an 'enclosed' mutable reference to the child foo - doesn't work !!!
    pub fn get_handle_in_enum(&mut self, id: usize) -> Handle {
        Handle::Foo(self.child_foos.get_mut(&id).unwrap())
    }
}

// Note: Compiler demands &'a mut Foo<'a>. Q: why is the &'a mut needed 
// (i.e. why can't it be &mut Foo<'a>)?
enum Handle<'a> {
    Foo(&'a mut Foo<'a>),
}

First question: Why am I forced to use &'a mut Foo<'a> in the enum definition (I would like to be able to use &mut Foo<'a> instead). I'm sure there is a good reason, but I suspect that this is responsible for the resulting compiler errors.

Second question: When I compile the code I some very confusing errors, including one where the compiler says it found what it was expecting?!

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/main.rs:39:9
   |
39 |         Handle::Foo(self.child_foos.get_mut(&id).unwrap())
   |         ^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined here...
  --> src/main.rs:38:31
   |
38 |     pub fn get_handle_in_enum(&mut self, id: usize) -> Handle {
   |                               ^^^^^^^^^
note: ...so that the types are compatible
  --> src/main.rs:39:9
   |
39 |         Handle::Foo(self.child_foos.get_mut(&id).unwrap())
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: expected `Handle<'_>`
              found `Handle<'_>`
note: but, the lifetime must be valid for the lifetime `'a` as defined here...
  --> src/main.rs:30:6
   |
30 | impl<'a> Foo<'a> {
   |      ^^
note: ...so that the expression is assignable
  --> src/main.rs:39:21
   |
39 |         Handle::Foo(self.child_foos.get_mut(&id).unwrap())
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: expected `&mut Foo<'_>`
              found `&mut Foo<'a>`

For more information about this error, try `rustc --explain E0495`.
error: could not compile `enum_mut_borrow` due to previous error

Any explanation or suggestions for how to use enums properly in this situation would be hugely appreciated (in my real code I need more variants of the enum each with its own mutable borrow)!

Upvotes: 0

Views: 57

Answers (1)

jthulhu
jthulhu

Reputation: 8678

You are not forced to put &'a mut Foo<'a>. You are forced to have an explicit lifetime: all borrows (exclusive or not) have an associated lifetime, but most of the time Rust doesn't require you to explicitly write it. Type definition doesn't fall in that category, so you need to explicitly write it:

enum Handle<'a, 'b> {
    Foo(&'b mut Foo<'a>),
}

this way, you dissociate the borrow's lifetime from Foo's.

To make this work, you also need to explicitly name the lifetime of &mut self in get_handle_in_enum:

fn get_handle_in_enum<'b>(&'b mut self, id: usize) -> Handle<'a, 'b> {
    ...
}

See the playground.

Upvotes: 1

Related Questions