Mercurial > lbo > hg > memoize
changeset 94:7d8d8525f957
[#18] Add custom hashers
author | blyxyas <blyxyas@gmail.com> |
---|---|
date | Fri, 13 Jan 2023 23:10:02 +0100 |
parents | db1c1e6b5642 |
children | 3d9facf8c639 |
files | Cargo.toml README.md examples/ahash.rs examples/custom_hasher.rs examples/full_featured.rs examples/fxhash.rs inner/src/lib.rs |
diffstat | 7 files changed, 147 insertions(+), 7 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.toml Fri Jan 13 17:21:02 2023 +0100 +++ b/Cargo.toml Fri Jan 13 23:10:02 2023 +0100 @@ -16,9 +16,14 @@ lazy_static = "1.4" lru = { version = "0.7", optional = true } +[dev-dependencies] + +rustc-hash = "1.1.0" +ahash = "0.8.2" + [workspace] members = ["inner/"] [features] -default = [] +default = ["full"] full = ["lru", "memoize-inner/full"]
--- a/README.md Fri Jan 13 17:21:02 2023 +0100 +++ b/README.md Fri Jan 13 23:10:02 2023 +0100 @@ -118,6 +118,18 @@ The cached value will never be older than duration provided and instead recalculated on the next request. +You can also specifiy a **custom hasher**, like [AHash](https://github.com/tkaitchuck/aHash) using `CustomHasher`. + +```rust +#[memoize(CustomHasher: ahash::HashMap)] +``` + +As some hashers initializing functions other than `new()`, you can specifiy a `HasherInit` function call: + +```rust +#[memoize(CustomHasher: FxHashMap, HasherInit: FxHashMap::default())] +``` + ### Flushing If you memoize a function `f`, there will be a function called
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/ahash.rs Fri Jan 13 23:10:02 2023 +0100 @@ -0,0 +1,26 @@ + +use memoize::memoize; +use ahash::{HashMap, HashMapExt}; + +#[cfg(feature = "full")] +#[memoize(CustomHasher: HashMap)] +fn hello() -> bool { + println!("hello!"); + true +} + +#[cfg(feature = "full")] +fn main() { + // `hello` is only called once here. + assert!(hello()); + assert!(hello()); + memoized_flush_hello(); + // and again here. + assert!(hello()); +} + +#[cfg(not(feature = "full"))] +fn main() { + println!("Use the \"full\" feature to execute this example"); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/custom_hasher.rs Fri Jan 13 23:10:02 2023 +0100 @@ -0,0 +1,26 @@ +//! Reproduces (verifies) issue #17: Panics when used on fn without args. + +use memoize::memoize; + +#[cfg(feature = "full")] +#[memoize(CustomHasher: std::collections::HashMap)] +fn hello() -> bool { + println!("hello!"); + true +} + +#[cfg(feature = "full")] +fn main() { + // `hello` is only called once here. + assert!(hello()); + assert!(hello()); + memoized_flush_hello(); + // and again here. + assert!(hello()); +} + +#[cfg(not(feature = "full"))] +fn main() { + println!("Use the \"full\" feature to execute this example"); +} +
--- a/examples/full_featured.rs Fri Jan 13 17:21:02 2023 +0100 +++ b/examples/full_featured.rs Fri Jan 13 23:10:02 2023 +0100 @@ -38,4 +38,4 @@ println!("result: {:?}", hello("ABC".to_string())); // Same as refreshed println!("result: {:?}", memoized_original_hello("ABC".to_string())); } -} +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/fxhash.rs Fri Jan 13 23:10:02 2023 +0100 @@ -0,0 +1,26 @@ + +use memoize::memoize; +use rustc_hash::FxHashMap; + +#[cfg(feature = "full")] +#[memoize(CustomHasher: FxHashMap, HasherInit: FxHashMap::default())] +fn hello() -> bool { + println!("hello!"); + true +} + +#[cfg(feature = "full")] +fn main() { + // `hello` is only called once here. + assert!(hello()); + assert!(hello()); + memoized_flush_hello(); + // and again here. + assert!(hello()); +} + +#[cfg(not(feature = "full"))] +fn main() { + println!("Use the \"full\" feature to execute this example"); +} +
--- a/inner/src/lib.rs Fri Jan 13 17:21:02 2023 +0100 +++ b/inner/src/lib.rs Fri Jan 13 23:10:02 2023 +0100 @@ -1,7 +1,7 @@ #![crate_type = "proc-macro"] #![allow(unused_imports)] // Spurious complaints about a required trait import. -use syn::{self, parse, parse_macro_input, spanned::Spanned, Expr, ItemFn}; +use syn::{self, parse, parse_macro_input, spanned::Spanned, Expr, ExprCall, ItemFn, Path}; use proc_macro::TokenStream; use quote::{self, ToTokens}; @@ -10,6 +10,8 @@ syn::custom_keyword!(Capacity); syn::custom_keyword!(TimeToLive); syn::custom_keyword!(SharedCache); + syn::custom_keyword!(CustomHasher); + syn::custom_keyword!(HasherInit); syn::custom_punctuation!(Colon, :); } @@ -18,6 +20,8 @@ lru_max_entries: Option<usize>, time_to_live: Option<Expr>, shared_cache: bool, + custom_hasher: Option<Path>, + custom_hasher_initializer: Option<ExprCall>, } #[derive(Clone)] @@ -25,6 +29,8 @@ LRUMaxEntries(usize), TimeToLive(Expr), SharedCache, + CustomHasher(Path), + HasherInit(ExprCall), } // To extend option parsing, add functionality here. @@ -60,6 +66,18 @@ input.parse::<kw::SharedCache>().unwrap(); return Ok(CacheOption::SharedCache); } + if la.peek(kw::CustomHasher) { + input.parse::<kw::CustomHasher>().unwrap(); + input.parse::<kw::Colon>().unwrap(); + let cap: syn::Path = input.parse().unwrap(); + return Ok(CacheOption::CustomHasher(cap)); + } + if la.peek(kw::HasherInit) { + input.parse::<kw::HasherInit>().unwrap(); + input.parse::<kw::Colon>().unwrap(); + let cap: syn::ExprCall = input.parse().unwrap(); + return Ok(CacheOption::HasherInit(cap)); + } Err(la.error()) } } @@ -74,6 +92,10 @@ match opt { CacheOption::LRUMaxEntries(cap) => opts.lru_max_entries = Some(cap), CacheOption::TimeToLive(sec) => opts.time_to_live = Some(sec), + CacheOption::CustomHasher(hasher) => opts.custom_hasher = Some(hasher), + CacheOption::HasherInit(init) => { + opts.custom_hasher_initializer = Some(init) + } CacheOption::SharedCache => opts.shared_cache = true, } } @@ -94,6 +116,12 @@ value_type: proc_macro2::TokenStream, ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { // This is the unbounded default. + if let Some(hasher) = &_options.custom_hasher { + return ( + quote::quote! { #hasher<#key_type, #value_type> }, + quote::quote! { #hasher::new() }, + ); + } ( quote::quote! { std::collections::HashMap<#key_type, #value_type> }, quote::quote! { std::collections::HashMap::new() }, @@ -131,10 +159,25 @@ }; // This is the unbounded default. match options.lru_max_entries { - None => ( - quote::quote! { std::collections::HashMap<#key_type, #value_type> }, - quote::quote! { std::collections::HashMap::new() }, - ), + None => { + if let Some(hasher) = &options.custom_hasher { + if let Some(hasher_init) = &options.custom_hasher_initializer { + return ( + quote::quote! { #hasher<#key_type, #value_type> }, + quote::quote! { #hasher_init }, + ); + } else { + return ( + quote::quote! { #hasher<#key_type, #value_type> }, + quote::quote! { #hasher::new() }, + ) + } + } + ( + quote::quote! { std::collections::HashMap<#key_type, #value_type> }, + quote::quote! { std::collections::HashMap::new() }, + ) + } Some(cap) => ( quote::quote! { ::memoize::lru::LruCache<#key_type, #value_type> }, quote::quote! { ::memoize::lru::LruCache::new(#cap) }, @@ -195,6 +238,8 @@ * `#[memoize(TimeToLive: Duration::from_secs(2))]`. In that case, cached value will be actual * no longer than duration provided and refreshed with next request. If you prefer chrono::Duration, * it can be also used: `#[memoize(TimeToLive: chrono::Duration::hours(9).to_std().unwrap()]` + * + * You can also specify a custom hasher: `#[memoize(CustomHasher: ahash::HashMap)]`, as some hashers don't use a `new()` method to initialize them, you can also specifiy a `HasherInit` parameter, like this: `#[memoize(CustomHasher: FxHashMap, HasherInit: FxHashMap::default())]`, so it will initialize your `FxHashMap` with `FxHashMap::default()` insteado of `FxHashMap::new()` * * This mechanism can, in principle, be extended (in the source code) to any other cache mechanism. *