-1

So I have a sort of a database struct which stores data for my app. When the data is dirty, it fetches it from the database and stores it inside of the struct. Either way, when this data is requested (Vec<&str>), we will have an existing String stored somewhere inside the database struct.

To the outside, it should look like this database is not mutating itself, because it should just look like it's just fetching data from the database. However, on the inside, it's actually storing some cache so that it doesn't have to fetch data every frame. So, I use a RefCell<Vec<String>> to store the cache.

Ok, so now I should be able to just get a Vec<&str>, where the &str is referencing that String that's stored in our database struct.

However, this cache is not guaranteed to always exist. For example, when it's invalidated, and the data needs to be fetched again. So actually, we have a RefCell<Option<Vec<String>>>:

use std::cell::RefCell;

struct Connection {}

pub struct Database {
    db: Connection,
    projects_cache: RefCell<Option<Vec<String>>>,
    // ...
}

Unfortunately, I can't seem to do this:

impl Database {
    pub fn projects(&self) -> Result<Vec<&str>, ()> {
        if self.projects_cache.borrow().is_some() {
            let cached: &Vec<String> = &*self.projects_cache.borrow().as_ref().unwrap();
            return Ok(cached.iter().map(|name| name.as_str()).collect());
        }
        // if not, then fetch data from the database and store it in the cache
        // ...
        unimplemented!()
    }
}

Apparently this is a temporary value.

error[E0515]: cannot return value referencing temporary value
  --> src/lib.rs:15:20
   |
14 |             let cached: &Vec<String> = &*self.projects_cache.borrow().as_ref().unwrap();
   |                                          ---------------------------- temporary value created here
15 |             return Ok(cached.iter().map(|name| name.as_str()).collect());
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

So my question is:

  1. Why is this value temporary? The String exists. We're just trying to take a reference to it. So why would it be temporary?

  2. How do I do this idiomatically? I'm not attached to this method at all. All I care about is that: I can store a cache; the fetch function takes &self and appears immutable from the outside; and that I don't have to make a brand new copy of String every single frame, for performance reasons.

trent
  • 25,033
  • 7
  • 51
  • 90
Thor Correia
  • 1,159
  • 1
  • 12
  • 20
  • Thanks for the suggestion — yes but also no. I saw this post before but it didn't satisfy me. The solution is ugly and it makes me think that this is not the optimal solution. I am more interested in what a more idiomatic approach might look like, whether or not that uses `RefCell` like this. – Thor Correia May 25 '20 at 10:47
  • Additionally neither of those options return the interior in the way that I'd like. The top answer returns some custom struct and the next one returns some `Ref` struct. I would like to completely hide this `RefCell` implementation, so. – Thor Correia May 25 '20 at 10:49
  • Finally, I also asked about the specifics of why this value is considered temporary which is not answered on this thread. – Thor Correia May 25 '20 at 10:50
  • 1
    I don't feel you have tried to understand *why* `Ref` is important here. The `Ref` *is* the temporary, as the error message suggests (note it underlines the entire expression `self.projects_cache.borrow()`). `Ref` is how `RefCell` tracks borrows at runtime. If you *could* return a `Vec<&str>`, the `RefCell` would have no way to know when the original `Option>` is no longer borrowed (note you can actually do this safely on nightly using [`Ref::leak`](https://doc.rust-lang.org/std/cell/struct.Ref.html#method.leak), but doing so means you can never `borrow_mut` again). – trent May 25 '20 at 11:42

1 Answers1

2

A RefCell can be mutated through a shared reference because of the runtime checks it performs - It makes sure that there aren't any other references being used by internally keeping track of the number of live references to it (The reference count)

This is implemented with the Ref struct - so long as it exists, you may read the contents of the RefCell through it. Its destructor decrements the reference count, notifying the RefCell that it won't be used again.

This is the reason why you can't give your users normal references (&str) to the contents of the RefCell - there's nothing stopping it from allowing its contents to be mutated. In this case, the promise that the String will exist is only valid as long as your Ref is in scope.

However, you can encapsulate the Ref as an implementation detail. By wrapping the Ref in a Project type, you can let users interact with a simpler API:

pub struct Project<'a>(Ref<'a, String>);

impl Project<'_> {
    pub fn name(&self) -> &str {
        self.0.as_str()
    }
}

Here's an example

trent
  • 25,033
  • 7
  • 51
  • 90
Plecra
  • 156
  • 3
  • 1
    Thanks! Is there are a more idiomatic approach? Would it be considered more proper to instead return a clone despite overhead? Ideally I would like this to be functionally equivalent to reading a field of an immutable struct, as if that field had always existed. – Thor Correia May 25 '20 at 19:57