#Caching data

78 messages · Page 1 of 1 (latest)

cloud cove
#

I'm making an API wrapper using Rust. I have a method products::all() which returns a vector of Product structs. This works fine but wouldn't it be best to cache the data once they call the method and if they do it again check whether the data was cached to return the vector? It takes a good while to get the data and certain functions like of_category() calls all() to filter it.

I'm thinking of implementing a mutable static variable that will have all the Product structs, something like

// products.rs
static mut products: Vec<Product> = Vec::new();

pub async fn all() -> Vec<Product> {
  if !products.len() > 10 {
    products = // load data
  }

  return products;
}
gleaming thistle
#

Mutable statics are very unsafe. If you want data that's loaded once, you can use once_cell:sync::Lazy

use once_cell::sync::Lazy;
static PRODUCTS: Lazy<Vec<Product>> = Lazy::new(|| {
  return todo!(); // load data here
});

You can then use PRODUCTS in your code and the first time it is accessed it will be initialised by calling the closure

cloud cove
#

I have to put the loading statements in todo!()?

gleaming thistle
#

Oh, not sure if you can have async functions in lazy, if that matters

gleaming thistle
cloud cove
#

It cannot find module once_cell

gleaming thistle
#

You need to add once_cell to your Cargo.toml

cloud cove
#

You can't have await in there

thick lichen
#

actually, a Lazy won't work here

#

use a OnceCell directly

cloud cove
#

OnceCell<Vec<Product>>?

thick lichen
#

yes

#

so something like this

gleaming thistle
thick lichen
#

yep - lazies only take closures and non-async funcs

#

apparently this exists

zinc flickerBOT
#

Async single assignment cells and lazy values.

Version

0.4.0

Downloads

8 075

thick lichen
#

but i doubt it's worth using it over just setting the value of the once cell in the async function

cloud cove
#

How would I create an instance of OnceCell?

thick lichen
#

OnceCell::new()

cloud cove
#

So in all() I would do

async fn all() -> Vec<Product> {
  return PRODUCTS.get_or_init(|| {
    return //load
  })
}

?

thick lichen
#

nope

#

get_or_init takes a regular closure so you cannot await in them

#

the way i'd do it is something like

#
pub async fn all() -> &[Product] {
    static CACHE: OnceCell<Vec<Product>> = OnceCell::new();

    if CACHE.get().is_none() {
        let cache =  /* load */;
        CACHE.set(cache).unwrap();
    }
    CACHE.get().unwrap();
}
thick lichen
# zinc flicker

though if you think this is a bit boilerplate-y, this crate is probably worth looking into

cloud cove
thick lichen
cloud cove
cloud cove
thick lichen
#

ah right the lifetime parameter

cloud cove
#

static?

thick lichen
#

should be &'static [Product]

#

yeah

#

also, please don't do return /* something */; at ends of functions

cloud cove
#

Is [Product] a vector?

thick lichen
#

it's more idiomatic to just return implicitly

cloud cove
#

VS code does not allow me to just

object // return
thick lichen
#

i use VS code and so do a lot of us

#

i don't get what you mean

cloud cove
#

Something must be up with mine, I get errors when I do

gleaming thistle
# cloud cove Shouldn't CACHE be outside the function?

Statics refer to data that exists for the whole lifetime of the program. By putting the static inside a function, it exists after the function ends but is only in scope in the function it's declared, which is (probably) what you want, if you don't want to be able to bypass the cache

gleaming thistle
cloud cove
cloud cove
thick lichen
#

should be CACHE.get().unwrap()

cloud cove
#

You can't have semicolons?

gleaming thistle
#

If you omit the semicolon for the final statement in a block, it implicitly returns it

thick lichen
#

yep - semicolons basically throw away the result of CACHE.get().unwrap() and return a ()

cloud cove
#

Oh, cool

gleaming thistle
cloud cove
#

So an array

thick lichen
#

well no

#

arrays are [T; N]s

#

slices ([T]) don't have a fixed length, so they can only exist behind references/some other kind of pointer

#

and that's why sometimes people refer to these (&[T]s) as "slices", even though they're more properly called references to slices

gleaming thistle
#

In this case, &[T] is reference to the data inside the vector

cloud cove
#

I cannot convert it into an iterator

gleaming thistle
#

Can you paste the code? You cut it off in your screenshot

cloud cove
#

It's another function I use to filter

thick lichen
#

what's the error message?

cloud cove
#

a value of type Vec<product_info::Product> cannot be built from an iterator over elements of type &product_info::Product
the trait FromIterator<&product_info::Product> is not implemented for Vec<product_info::Product>

#

Nervermind, I return a &'static [Product]

#

Nevermind, still the same error

gleaming thistle
#

The problem is you're trying to return a Vec<Product> (a vector of owned Products) but what you have is a list of &Product, references to Products in the cache. You could return Vec<&Product> or use .cloned() to clone the data and get an owned list of products you can return

#
filter(|...| ...).cloned().collect()
#

You can't return a static slice here because you're creating a new collection of references which isn't a slice into the static slice

cloud cove
#

The Product references I return need a life time specifier

gleaming thistle
#

The lifetime will be static

cloud cove
#

Doesn't static mean it'll stay for the entirety of runtime?

gleaming thistle
#

Yeah

cloud cove
#

So the references will stay for runtime?

gleaming thistle
#

Yeah. The values will be created the first time they're accessed in the OnceCell, and will live until the program closes, so they're always valid to reference