Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased] - 2025-xx-yy
## [Unreleased] - xxxx-yy-zz

### Added

### Changed

### Removed

## [0.7.0] - 2025-09-09

### Changed

- Conditionally remove `Clone`able need or Err type. So in cases you pass `result` to `kash`, when the return is `Result`, your Err type doesn't need to be `Clone`able.

## [0.6.0] - 2025-09-08

### Changed
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["kash_macros"]

[package]
name = "kash"
version = "0.6.0"
version = "0.7.0"
authors = ["Omid Rad <omidmr@gmail.com>", "James Kominick <james@kominick.com>"]
description = "Function and method cache and memoization"
repository = "https://github.com/omid/kash"
Expand Down Expand Up @@ -51,7 +51,7 @@ disk_store = [
[dependencies]
async-trait = "0.1"
directories = { version = "6.0", optional = true }
kash_macros = { path = "kash_macros", version = "0.6" }
kash_macros = { path = "kash_macros", version = "0.7" }
moka = "0.12"
# moka = { version = "0.12" , path = "../moka/" }
once_cell = "1"
Expand Down
14 changes: 9 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@
# run using `cargo run --example=$EXAMPLE` and run standalone. All features are
# **enabled**
KASH_BASIC_EXAMPLES = basic \
complex \
in_impl \
once \
tokio
fib \
complex \
in_impl \
once \
tokio \
disk \
custom_error_clone \
custom_error_noclone
# Same as `KASH_BASIC_EXAMPLES`, but these examples require the `docker/redis`
# goal
KASH_REDIS_EXAMPLES = redis \
redis-async
redis-async

# Cargo command used to run `run`, `build`, `test`... Useful if you keep
# multiple cargo versions installed on your machine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ enum MyError {
Err,
}

#[kash(result)]
#[kash]
fn slow_fn(n: u32) -> Result<String, MyError> {
if n == 0 {
return Err(MyError::Err);
Expand Down
53 changes: 53 additions & 0 deletions examples/custom_error_noclone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)]
use kash::kash;
use std::{
sync::Arc,
thread::sleep,
time::{Duration, Instant},
};

enum MyError {
Err,
}

#[kash]
fn slow_fn(n: u32) -> Result<String, Arc<MyError>> {
if n == 0 {
return Err(Arc::new(MyError::Err));
}
sleep(Duration::new(1, 0));
slow_fn(n - 1)
}

#[kash(result)]
fn slow_fn_with_result_flag(n: u32) -> Result<String, MyError> {
if n == 0 {
return Err(MyError::Err);
}
sleep(Duration::new(1, 0));
slow_fn_with_result_flag(n - 1)
}

pub fn main() {
println!("Initial run...");
let now = Instant::now();
let _ = slow_fn(10);
println!("Elapsed: {}\n", now.elapsed().as_secs());

println!("Cached run...");
let now = Instant::now();
let _ = slow_fn(10);
println!("Elapsed: {}\n", now.elapsed().as_secs());

println!("Initial run...");
let now = Instant::now();
let _ = slow_fn_with_result_flag(10);
println!("Elapsed: {}\n", now.elapsed().as_secs());

println!("Cached run...");
let now = Instant::now();
let _ = slow_fn_with_result_flag(10);
println!("Elapsed: {}\n", now.elapsed().as_secs());

println!("done!");
}
5 changes: 5 additions & 0 deletions fib/kitchen_sink.rs → examples/fib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#![allow(
clippy::arithmetic_side_effects,
clippy::unwrap_used,
clippy::unnecessary_wraps
)]
use kash::kash;
use std::thread::{sleep, spawn};
use std::time::Duration;
Expand Down
2 changes: 1 addition & 1 deletion kash_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "kash_macros"
version = "0.6.0"
version = "0.7.0"
authors = [
"Omid Rad <omidmr@gmail.com>",
"csos95 <csoscss@gmail.com>",
Expand Down
11 changes: 9 additions & 2 deletions kash_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ use syn::{ItemFn, parse_macro_input};
/// In the attribute list below, `size`, `eviction_policy` are possible just if it's a memory cache.
///
/// # Attributes
///
/// - `name`: (optional, string) Specify the name for the generated cache. Defaults to CONSTANT_CASE name of the function
/// - `size`: (optional, string) Specify to keep the number of entries in the cache. Default to unbounded.
/// - `eviction_policy`: (optional, string) Specify the eviction policy, valid options are "lfu" (Least Frequently Used) and "lru" (Least Recently Used). Defaults to "lfu" and it's the most suitable policy for most cases.
/// - `eviction_policy`: (optional, string) Specify the eviction policy, valid options are "lfu" (Least Frequently Used) and "lru" (Least Recently Used). Defaults to "lfu".
/// - `ttl`: (optional, string) Specify a cache TTL in seconds. Defaults to unlimited amount of time.
/// - `key`: (optional, string) Specify a specific key to use. You need to define the following attributes for a custom `key`, e.g., `key(ty = "String", expr = r#"{ format!("{}:{}", arg1, arg2) }"#)`. By default, use all the arguments of the function as the key.
/// - `ty`: (string) Specify type of the key. E.g, `ty = "String"`
/// - `expr`: (string expr) Specify an expression used to generate a cache key.
/// E.g., `expr = r#"{ format!("{}:{}", arg1, arg2) }"#`.
/// - `result`: (optional) If your function returns a `Result`, only cache `Ok` values returned by the function.
/// - `result`: (optional) If your function returns a `Result`, only cache `Ok` values returned by the function. (Read the note below about `Result`)
/// - `option`: (optional) If your function returns an `Option`, only cache `Some` values returned by the function.
/// - `in_impl`: (optional) Set it if your function is defined in an `impl` block, otherwise not.
/// - `redis`: (optional) Store cached values in Redis.
Expand All @@ -40,6 +41,12 @@ use syn::{ItemFn, parse_macro_input};
/// to give more control over the connection to the `disk` cache, i.e., useful for controlling the rate at which the cache syncs to disk.
/// See the docs of `kash::stores::DiskCacheBuilder::connection_config` for more info.
///
/// # Note
///
/// - If your function returns a `Result`:
/// - In cases you define `result` in `kash`, the `Err` variant can be anything.
/// - But in cases you want to cache the `Err` variant too (by _not_ defining `result` in `kash`), the error type must be `Clone`able. So in this case, you may need to `Arc` your error type.
///
#[proc_macro_attribute]
pub fn kash(args: TokenStream, input: TokenStream) -> TokenStream {
let args = match MacroArgs::try_from(args) {
Expand Down
39 changes: 21 additions & 18 deletions kash_macros/src/mem/cache_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,34 +46,37 @@ impl ToTokens for CacheFn<'_> {
} else {
quote! {}
};
let mut function_call = quote! {
#call_prefix #no_cache_fn_ident(#(#maybe_with_self_names),*)
let function_call = quote! {
#call_prefix #no_cache_fn_ident(#(#maybe_with_self_names),*) #may_await
};

if sig.asyncness.is_none() {
function_call = quote! {
|| #function_call
}
}
let may_return = if self.args.option {
quote!(?)
} else {
quote!()
};

let (insert, may_return_early, may_wrap) = match (self.args.result, self.args.option) {
(false, false) => (quote!(.or_insert_with(#function_call)), quote!(), quote!()),
(true, false) => (
quote!(.or_try_insert_with(#function_call)),
quote!(.map_err(|e| e.deref().clone())?),
quote!(Ok),
),
let (insert, may_wrap) = match (self.args.result, self.args.option) {
(false, false) => (quote!(.or_insert(#function_call) #may_await), quote!()),
(true, false) => (quote!(.or_insert(#function_call?) #may_await), quote!(Ok)),
(false, true) => (
quote!(.or_optionally_insert_with(#function_call) ),
quote!(?),
quote!(.or_optionally_insert_with(|| #function_call) #may_await),
quote!(Some),
),
_ => unreachable!("All errors should be handled in the `MacroArgs` validation methods"),
};

let do_set_return_block = quote! {
use std::ops::Deref;
#may_wrap (#local_cache.entry_by_ref(&#key_expr) #insert #may_await #may_return_early .into_value() .clone())
let val = #local_cache.get(&#key_expr) #may_await;
if let Some(val) = val {
#may_wrap (val)
} else {
#may_wrap (#local_cache
.entry_by_ref(&#key_expr)
#insert
#may_return
.into_value())
}
};

let expanded = quote! {
Expand Down