Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cached_result! doesn't work with async function #112

Closed
tsukit opened this issue Apr 22, 2022 · 8 comments
Closed

cached_result! doesn't work with async function #112

tsukit opened this issue Apr 22, 2022 · 8 comments

Comments

@tsukit
Copy link

tsukit commented Apr 22, 2022

The following program fails to compile.

#[tokio::main]
async fn main() {
    compute_thing(10).await.unwrap();
}

cached_result! {
    CACHE: cached::TimedSizedCache<i32, i32> = cached::TimedSizedCache::with_size_and_lifespan(10, 10);
    async fn compute_thing(x: i32) -> std::result::Result<i32, String> = {
        Ok(x * x)
    }
}

The error message is

error: expected one of `where` or `{`, found `{ Ok(x * x) }`
  --> src/main.rs:22:1
   |
22 | / cached_result! {
23 | |     CACHE: cached::TimedSizedCache<i32, i32> = cached::TimedSizedCache::with_size_and_lifespan(10, 10);
24 | |     async fn compute_thing(x: i32) -> std::result::Result<i32, String> = {
25 | |         Ok(x * x)
26 | |     }
27 | | }
   | |_^ expected one of `where` or `{`
   |
   = note: this error originates in the macro `cached_result` (in Nightly builds, run with -Z macro-backtrace for more info)

It appears to be specific to async because removing async and await. It's like the macro for async case expands to invalid code.

@tsukit tsukit changed the title cache_result! doesn't work with async function cached_result! doesn't work with async function Apr 22, 2022
@jaemk
Copy link
Owner

jaemk commented Apr 22, 2022

Hi @tsukit , the declarative macros don't support as many features as the proc_macros do. See the docs for proc macro examples https://docs.rs/cached/latest/cached/proc_macro/index.html, or the examples dir https://github.com/jaemk/cached/tree/master/examples.

Your example would translate to

#[cached(result = true, time = 10, size = 10)]
async fn compute_thing(x: i32) -> std::result::Result<i32, String> {
    Ok(x * x)
}

@tsukit
Copy link
Author

tsukit commented Apr 25, 2022

Thanks @jaemk . Unfortunately that doesn't really work in actual code that I have. The sample code I provided is a bit different from what I actually have where the returned object is a struct. When using the macro as you suggested, there are two major problems.

  1. The time and size can't be programmatically set when the program starts. The values come from our configuration system and it's not a constant that can be statically set.

  2. The macro doesn't work with returned struct as value. With the revised code below, It gives error expected struct `MyStruct`, found `&MyStruct`

#[cached(result = true, time = 10, size = 10)]
async fn compute_thing(x: i32) -> std::result::Result<MyStruct, String> {
    Ok(MyStruct { value: x * x })
}

struct MyStruct {
    value: i32,
}

This seems to be the same as reported here. What might I have missed?

@jaemk
Copy link
Owner

jaemk commented Apr 25, 2022

@tsukit To address number 2.) Making your type Clone will fix the warning

expected struct MyStruct, found &MyStruct

#[derive(Clone)]
struct MyStruct {
    value: i32,
}

For issue 1.), you can provide a code block that will be used to construct the type on first use. Using your example, this would look like the following:

use cached::proc_macro::cached;
use cached::stores::TimedSizedCache;
use lazy_static::lazy_static;

struct Config {
    size: usize,
    lifespan: u64,
}
lazy_static! {
    static ref CONFIG: Config = {
        Config {
            size: 10,
            lifespan: 10,
        }
    };
}

#[cached(
    result = true,
    create = r#"{ TimedSizedCache::with_size_and_lifespan(CONFIG.size, CONFIG.lifespan) }"#,
    type = r#"TimedSizedCache<i32, MyStruct>"#
)]
async fn compute_thing(x: i32) -> std::result::Result<MyStruct, String> {
    Ok(MyStruct { value: x * x })
}

#[derive(Clone)]
struct MyStruct {
    value: i32,
}

#[tokio::test]
async fn test_cached() {
    compute_thing(2).await.unwrap();
}

@tsukit
Copy link
Author

tsukit commented Apr 26, 2022

Thanks! I am able to get it to work now with the suggestion. To clarify, is cached_result!thing in the past and shouldn't be used anymore?

@glitchy
Copy link

glitchy commented Apr 26, 2022

@jaemk I have a similar issue as above--my problem is that my code is in an impl and thus I cannot use the proc_macro--any ideas on how to make this inner fn async? (the build_and_cache fn in my code below)--

I currently get the following error:

  --> src/vendor/file.rs:83:9
   |
83 | /         cached_key_result! {
84 | |             JWKS: TimedCache<String, JwkSet> = TimedCache::with_lifespan(120);
85 | |             Key = { format!("{}", url) };
86 | |             async fn build_and_cache(
...  |
92 | |             }
93 | |         }
   | |_________^ expected one of `where` or `{`

Current code within the impl:

    async fn jwks(client: &ClientWithMiddleware, url: &str) -> Result<JwkSet> {

        cached_key_result! {
            JWKS: TimedCache<String, JwkSet> = TimedCache::with_lifespan(120);
            Key = { format!("{}", url) };
            async fn build_and_cache(
                client: &ClientWithMiddleware,
                url: &str
            ) -> Result<JwkSet> = {
                let jwks = client.get(url).send().await?.json::<JwkSet>().await?;
                Ok(cached::Return::new(jwks))
            }
        }

        build_and_cache(client, url).await
    }

Thanks!

@jaemk
Copy link
Owner

jaemk commented Apr 26, 2022

Hi @glitchy , there's no reason why you can't use the proc_macro here. The old declarative macros are really just kept around to be backwards compatible. Here's an example of what you could refactor your code to be

use cached::proc_macro::cached;
use cached::stores::TimedCache;
use lazy_static::lazy_static;

struct Config {
    lifespan: u64,
}
lazy_static! {
    static ref CONFIG: Config = Config { lifespan: 10 };
}

// placeholder for the type you're adding an impl for
struct MyStruct {
    value: i32,
}

// placeholder for `ClientWithMiddleware`
struct Client;

// placeholder for the `JwkSet` type
// return type needs to be Clone-able.
#[derive(Clone)]
struct Set(String);

impl MyStruct {
    async fn jwks(&self, client: &Client, url: &str) -> std::result::Result<Set, ()> {
        #[cached(
            result = true,
            type = r#"TimedCache<String, Set>"#,
            create = r#"{ TimedCache::with_lifespan(CONFIG.lifespan) }"#,
            convert = r#"{ url.to_string() }"#
        )]
        async fn build_and_cache(
            client: &Client,
            amount: i32,
            url: &str,
        ) -> std::result::Result<Set, ()> {
            // do stuff with client
            Ok(Set(url.to_string()))
        }
        build_and_cache(client, self.value, url).await
    }
}

#[tokio::test]
async fn test_cached() {
    let s = MyStruct { value: 2 };
    let c = Client;
    s.jwks(&c, "https://place.it").await.unwrap();
}

@glitchy
Copy link

glitchy commented Apr 27, 2022

++1 @jaemk. i see now where i went astray--ty!

@tsukit
Copy link
Author

tsukit commented Apr 27, 2022

Ok. The question has been addressed/answered. We can close this. Thanks @jaemk

@tsukit tsukit closed this as completed Apr 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants