0
votes

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?

1
You can't get &T from a RefCell<T> without keeping the Ref<T> around, its how RefCell knows when borrow() and borrow_mut() are allowed. See this Q&A.kmdreko

1 Answers

1
votes

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.