Rust struct impl return default field, lazily calculated

I have a struct that attempts to cache Sqlite query results and if they are None it will go and fetch the result fresh from the database.

The way I've implemented this is simply creating an impl with a method for each field, get_field(conn: &Conn), where Conn is a database connection.

My problem is readability, I want to just call get_field and it decides to either return the current cached field, or go and fetch (large blob) results and returns those:


impl MyStruct {

  fn fetch(&self, table: &str, column: &str, row: Option<i32>) -> Vec<f32> {
    ... perform query to return blob from Sqlite database...
  }

  fn get_field_x(&self, conn: &Conn) -> Vec<f32> {
    match &self.x { // not gonna work..
       Some(x_ref) => x_ref // oh no, this is a reference...
       None => fetch(..) // oh no, can't return reference to data created here...
    }
  }

}

The obvious work around is to just do a simple unwrap beforehand, which alleviates it from having to check if x is None:

let cached_or_live = my_struct.x.unwrap_or(my_struct.fetch(..., conn));

But now I have unwrap_or duplicated all over my code. Is there a way to still perform this check in an impl? I don't want to clone x_ref, as it can be large and that feels like a smelly work around.

This looks like the perfect use case for our mooing friend.

Playground demo

2 Likes

Wow, magic. And there's no major performance overhead to cow I should be concerned with? I am writing very large Vecs...

The "you can't return a reference to data created here" rule being bypassed so easily just makes me wonder at what cost this magic works for. :slight_smile:

The performance impact:

  • Cow is larger
  • You still need to clone everything if you want to mutate it.

Cow is basically the answer to "how can I return something that might be owned or might be borrowed".

1 Like

ah bummer, I do need to iterate these Vecs, so calling next() on an iter does require it be mutated...

This doesn't implement a cache / lazy initialization, though — if the field isn't set it fetches it on every call. Caching and returning a reference sounds like a use case for OnceCell.

1 Like

Iterating mutates the iterator, not (necessarily) what is being iterated over. Call Vec::iter() and you get an iterator which doesn't consume the Vec.

5 Likes

Yes. All I did was the minimum required to solve the borrowing problem. The actual cache is up to OP, and I don't believe that's the part they were having issue with.

It isn't bypassed. The returned reference borrows from self and has its lifetime tied to self, no reference to local variables is returned.

Cow isn't magic either, it's just an enum with a lifetime-annotated reference in one variant.

But once there is a cache, it will no longer make sense to conditionally return the value owned — it can and probably should be returned by reference (or, if needed for some reason, by cloned value). Thus Cow does not help when the intent is to fetch with caching.

Isn't it one of the intended use case for OnceCell ?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.