Search code examples
rustownershipinterior-mutabilityrefcell

"cannot return value referencing temporary value" and interior mutability in Rust


I have the following code in Rust:

pub struct RegExpFilter {
    ...
    regexp_data: RefCell<Option<RegexpData>>,
    ...
}

struct RegexpData {
    regexp: regex::Regex,
    string: String
}

...
    pub fn is_regexp_compiled(&self) -> bool {
        self.regexp_data.borrow().is_some()
    }

    pub fn compile_regexp(&self) -> RegexpData {
        ...
    }

    fn regexp(&self) -> &regex::Regex {
        if !self.is_regexp_compiled() { // lazy computation that mutates the struct
            self.regexp_data.replace(Some(self.compile_regexp()));
        }
        &self.regexp_data.borrow().as_ref().unwrap().regexp
    }
    
    pub fn matches(&self, location: &str) -> bool {
         self.regexp().find(location)
    }

regexp is calculated lazily, capturing &mut self i undesired so RefCell is used.

I'm getting the following message:

               &self.regexp_data.borrow().as_ref().unwrap().regexp
    |          ^-------------------------^^^^^^^^^^^^^^^^^^^^^^^^^
    |          ||
    |          |temporary value created here
    |          returns a value referencing data owned by the current function

The compiler message seems to be clear: Ref is temporarily created by borrow() and returned outside. However i believe Option (self.regexp_data) is owned by RefCell which is owned by the struct itself, so it should be fine to use it internally (since the function is not pub).

I've also tried the following (and it fails with the same message)

    fn regexp(&self) -> impl Deref<Target = regex::Regex> + '_ {
        if !self.is_regexp_compiled() {
            self.regexp_data.replace(Some(self.compile_regexp()));
        }
        Ref::map(self.regexp_data.borrow(), |it| &it.unwrap().regexp)
    }

How can i solve it?


Solution

  • You can fix the Ref::map version by using .as_ref() to convert the &Option<_> to a Option<&_> in order to to unwrap as a reference:

    fn regexp(&self) -> impl Deref<Target = regex::Regex> + '_ {
        if !self.is_regexp_compiled() {
            self.regexp_data.replace(Some(self.compile_regexp()));
        }
        Ref::map(self.regexp_data.borrow(), |it| &it.as_ref().unwrap().regexp)
                                                  // ^^^^^^^^
    }
    

    In this scenario, I'd advocate for using OnceCell from the once_cell crate:

    use once_cell::sync::OnceCell;
    
    pub struct RegexpData {
        regexp: regex::Regex,
        string: String,
    }
    
    pub struct RegExpFilter {
        regexp_data: OnceCell<RegexpData>,
    }
    
    impl RegExpFilter {
        pub fn compile_regexp(&self) -> RegexpData {
            unimplemented!()
        }
    
        fn regexp(&self) -> &regex::Regex {
            &self.regexp_data.get_or_init(|| self.compile_regexp()).regexp
        }
    }
    

    You can simply use get_or_init to get the same effect. OnceCell and Lazy (in the same crate) are very convenient for lazy-evaluation.

    See it on the playground.