changeset 97:525fcf2058ae

Merge pull request #23 from blyxyas/master [#18] Add custom hasher parameter
author Lewin Bormann <lbo@spheniscida.de>
date Sun, 15 Jan 2023 18:41:31 +0100
parents db1c1e6b5642 (current diff) 528ce50ec228 (diff)
children 0444f8e3099d
files
diffstat 8 files changed, 163 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/Cargo.toml	Fri Jan 13 17:21:02 2023 +0100
+++ b/Cargo.toml	Sun Jan 15 18:41:31 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	Sun Jan 15 18:41:31 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	Sun Jan 15 18:41:31 2023 +0100
@@ -0,0 +1,24 @@
+use ahash::{HashMap, HashMapExt};
+use memoize::memoize;
+
+#[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	Sun Jan 15 18:41:31 2023 +0100
@@ -0,0 +1,32 @@
+//! 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
+}
+
+// ! This will panic because CustomHasher and Capacity are being used.
+// #[cfg(feature = "full")]
+// #[memoize(CustomHasher: std::collections::HashMap, Capacity: 3usize)]
+// fn will_panic(a: u32, b: u32) -> u32 {
+//     a + b
+// }
+
+#[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/fxhash.rs	Sun Jan 15 18:41:31 2023 +0100
@@ -0,0 +1,24 @@
+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/examples/patterns.rs	Fri Jan 13 17:21:02 2023 +0100
+++ b/examples/patterns.rs	Sun Jan 15 18:41:31 2023 +0100
@@ -19,7 +19,7 @@
 fn main() {
     // `manhattan_distance` is only called once here.
     assert_eq!(manhattan_distance((1, 1), (1, 3)), 2);
-    
+
     // Same with `get_value`.
     assert_eq!(get_value(OnlyOne::Value(0)), 0);
 }
--- a/inner/src/lib.rs	Fri Jan 13 17:21:02 2023 +0100
+++ b/inner/src/lib.rs	Sun Jan 15 18:41:31 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,8 @@
             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,10 +114,17 @@
         value_type: proc_macro2::TokenStream,
     ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
         // This is the unbounded default.
-        (
-            quote::quote! { std::collections::HashMap<#key_type, #value_type> },
-            quote::quote! { std::collections::HashMap::new() },
-        )
+        if let Some(hasher) = &_options.custom_hasher {
+            return (
+                quote::quote! { #hasher<#key_type, #value_type> },
+                quote::quote! { #hasher::new() },
+            );
+        } else {
+            (
+                quote::quote! { std::collections::HashMap<#key_type, #value_type> },
+                quote::quote! { std::collections::HashMap::new() },
+            )
+        }
     }
 
     /// Returns names of methods as TokenStreams to insert and get (respectively) elements from a
@@ -131,14 +158,35 @@
         };
         // 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() },
-            ),
-            Some(cap) => (
-                quote::quote! { ::memoize::lru::LruCache<#key_type, #value_type> },
-                quote::quote! { ::memoize::lru::LruCache::new(#cap) },
-            ),
+            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) => {
+                if let Some(_) = &options.custom_hasher {
+                    panic!("You can't use LRUMaxEntries and a Custom Hasher. Remove `LRUMaxEntries` from the attribute");
+                } else {
+                    (
+                        quote::quote! { ::memoize::lru::LruCache<#key_type, #value_type> },
+                        quote::quote! { ::memoize::lru::LruCache::new(#cap) },
+                    )
+                }
+            }
         }
     }
 
@@ -196,6 +244,8 @@
  * 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.
  *
  * `memoized_flush_<function name>()` allows you to clear the underlying memoization cache of a
--- a/src/lib.rs	Fri Jan 13 17:21:02 2023 +0100
+++ b/src/lib.rs	Sun Jan 15 18:41:31 2023 +0100
@@ -1,5 +1,5 @@
+pub use ::lazy_static;
 pub use ::memoize_inner::memoize;
-pub use ::lazy_static;
 
 #[cfg(feature = "full")]
 pub use ::lru;