Need help with a lifetime-related error message that seems unhelpful

I get a compilation error related to lifetimes that I'm having trouble undersanding. Perhaps one of you can help? Here's the code (I pared it down as best I could):

use std::collections::HashMap;

pub enum BaseError {
    TBD,
}

#[derive(Debug, Clone, PartialEq)]
pub enum RawValue {
    TBD,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Value<'a> {
    Sequence(&'a Vec<Value<'a>>),
}

#[derive(Debug, Clone, PartialEq)]
pub enum InternalValue<'a> {
    Base(Value<'a>),
    ToBeDerived(RawValue),
    TBD,
}

pub enum ThingError {
    InvalidPath,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Thing<'a> {
    data: HashMap<String, InternalValue<'a>>,
}

impl<'a> Thing<'a> {
    fn derive(&self, _node: &RawValue) -> Result<InternalValue, ThingError> {
        todo!("derive");
    }

    fn use_path(&mut self, _node: &RawValue) -> Result<InternalValue, ThingError> {
        todo!("use_path");
    }

    pub(crate) fn compute_path(&self, _s: &str) -> Result<RawValue, BaseError> {
        todo!("compute_path");
    }

    pub fn get(&mut self, key: &str) -> Result<Value, ThingError> {
        let iv = match self.data.get(key) {
            None => {
                match self.compute_path(key) {
                    Err(_) => Err(ThingError::InvalidPath),
                    Ok(node) => self.use_path(&node),
                }
            }
            Some(v) => Ok(v.clone()),
        };

        match iv {
            Err(e) => Err(e),
            Ok(v) => match v {
                InternalValue::Base(cv) => Ok(cv),
                InternalValue::ToBeDerived(av) => {
                    let _v = self.derive(&av);

                    todo!("handle v");
                }
                _ => todo!("other cases"),
            },
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
  --> src/lib.rs:62:30
   |
46 |     pub fn get(&mut self, key: &str) -> Result<Value, ThingError> {
   |                - let's call the lifetime of this reference `'1`
...
51 |                     Ok(node) => self.use_path(&node),
   |                                 ---- mutable borrow occurs here
...
60 |                 InternalValue::Base(cv) => Ok(cv),
   |                                            ------ returning this value requires that `*self` is borrowed for `'1`
61 |                 InternalValue::ToBeDerived(av) => {
62 |                     let _v = self.derive(&av);
   |                              ^^^^ immutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

I understand that the problem is due to line 60, as the message says, but what isn't clear to me is why, and how to get around it. Commenting out that line allows compilation to complete, but that isn't what I want.

Basically, an internal hash map holds values that are either "base" or "derived", and if they are "derived", some additional computation needs to happen. I've no problem using references, cloning returned values etc. but I'm just not sure where I need to apply those - applying in the obvious places hasn't made a difference, but maybe what's obvious to me isn't obvious enough!

The problem seems to lie with these two methods:

fn derive(&self, _node: &RawValue) -> Result<InternalValue, ThingError> {
    todo!("derive");
}

fn use_path(&mut self, _node: &RawValue) -> Result<InternalValue, ThingError> {
    todo!("use_path");
}

Firstly, in Rust 2018, omitting lifetimes from types was deprecated - the preferred syntax is now:

fn derive(&self, _node: &RawValue) -> Result<InternalValue<'_>, ThingError> {
    todo!("derive");
}

fn use_path(&mut self, _node: &RawValue) -> Result<InternalValue<'_>, ThingError> {
    todo!("use_path");
}

If you omit the lifetime or use '_, it basically means you're telling the compiler: 'infer the lifetime for me' . You were probably expecting it to infer 'a, which is the lifetime of the references stored within the HashMap in your struct. Unfortunately, due to the lifetime elison rules, your code is actually equivilant to:

fn derive<'b>(&'b self, _node: &RawValue) -> Result<InternalValue<'b>, ThingError> {
    todo!("derive");
}

fn use_path<'b>(&'b mut self, _node: &RawValue) -> Result<InternalValue<'b>, ThingError> {
    todo!("use_path");
}

You're tying the lifetime of the return value to the lifetime of the method's self reference, which is not at all what you were hoping for! To fix this, you need to be more explicit about what lifetime you want to use:

fn derive(&self, _node: &RawValue) -> Result<InternalValue<'a>, ThingError> {
    todo!("derive");
}

fn use_path(&mut self, _node: &RawValue) -> Result<InternalValue<'a>, ThingError> {
    todo!("use_path");
}

This compiles fine :tada:

In general, if I run into weird lifetime errors, one of the first things I like to do is remove inference from the equasion by manually annotating the function. If that fixes the issue, it means that the inferred lifetimes weren't what I expected. If it doesn't fix the issue, it's more likely I have an actual issue on my hands.

2 Likes

Interestingly, another (less correct) fix would have been to make use_path take &self rather than &mut self, which is what I had come up with before I saw this answer. I had not realized that the mut-ness of a reference was linked to lifetime inference in this way. i.e. the InternalValue<'_> was interpreted as holding onto the &mut self reference, even though it doesn't have a &mut anywhere in it.

Yes, yes, I was! Thanks so much for explaining what I did wrong.

1 Like

I couldn't do that in the real code this example is drawn from, because I do need to mutate self in there.

1 Like