cached
cached copied to clipboard
Cache member functions?
Hi.
I don't know why this isn't possible yet, and while searching the issues I did not find something that answered my questions: Why is there no caching for member functions?
Consider:
pub struct Foo(i32, i32);
impl Foo {
#[cached]
pub fn get_first(&self) -> i32 { /* ... */ }
#[cached]
pub fn get_snd(&self) -> i32 { /* ... */ }
}
That's actually not that hard to cache, is it? Maybe I'm missing some important pieces here, I don't know...
That being said, one could easily craft a type which just calls (cached) free private functions, like so:
pub struct Foo(i32, i32);
impl Foo {
pub fn get_first(&self) -> i32 { foo_get_first(self) }
pub fn get_snd(&self) -> i32 { foo_get_snd(self) }
}
#[cached]
fn foo_get_first(f: &Foo) -> i32 { /* ... */ }
#[cached]
fn foo_get_snd(f: &Foo) -> i32 { /* ... */ }
right? That'd yield the same, AFAICS?
There’s some context in #16 . The main complication being that the macros expand to a function definition and a static “once_cell” at the same level, and the static def of the once_cell can’t exist inside of an impl block. There would be some other caveats too like the type of “self” and “&self” would have to impl hash+eq+clone or you would always need to specify the “convert” argument to the macro so it doesn’t use “self” as part of the cache key
Would there be a way to tackle the issues you describe with an attribute macro? Like, you annotate your struct "Hey, we gotta get some caching in here" and then generate cache calling functions for that cache? Consider:
#[derive(cached::Cacheable)]
// or #[cached::make_cachable] not sure which one you'd need
// generating the static once cell here for Foo
struct Foo(i32);
impl Foo {
#[cached]
pub fn get(&self) -> i32 {
self.0
}
}
to generate a Foo::get()
that calls the cache that was generated for Foo
?
That's probably doable with the (similar) caveat that it would only work for structs defined at the top level so the cache can be defined. Another option that was touched on in #16 is to allow the cached macro to take a callable (probably by string like the "convert" argument) which return the cache that should be used so that you can annotate a method as cached without requiring a global static cache to be defined at the same time
Hi, What about a struct-local cache? So with a definition like this:
#[derive(Cacheable)]
struct Foo {
x: i32
}
impl Foo {
#[cached_method]
pub fn multiply(&self, y: i32) -> i32 {
self.x * y
}
}
The expanded code would look something like this:
struct Foo {
x: i32,
CACHES: HashMap<String, CacheType>,
}
impl Foo {
pub fn multiply(&self, y: i32) -> i32 {
if let Some(cache) = CACHES.get("MULTIPLY") {
if let Some(value) = cache.get((self, y)) {
return value;
}
} else {
CACHES.insert("MULTIPLY", CacheType::new());
}
result = self.x * y;
CACHES.get_mut("MULTIPLY").unwrap().set((self, y), result);
result
}
}
Caveats:
- Adding a new member to a struct breaks existing functions which construct this struct. User would need to manually take care of the initialization of the CACHES member, as I don't think it's possible to solve that with a macro (for every function with initializes it, so... no)
- possible duplication of cached values when there are multiple (equal, e.g. cloned) instances of this struct.
- Additional look-ups @ runtime due to the HashMap-Lookup
The issue with the new member and data duplication could be solved with a global/static HashMap (CACHES_STRUCT_FOO
?), ofc also only for top-level structs.
And yes, I also have a use-case where I want to cache the results of methods, but after writing all this and thinking about it, I will probably make a custom implementation with private cache-members, or rewrite my methods to functions which take MyType
as their first argument.