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.
  *