0

I have an async function save that has a Save struct as argument which optionally contains an async function (validator). The problem is that the following code only works when Some(..) is specified, with None the compiler throws an error.

use std::future::Future;

trait SomeTrait {}

enum SomeError {}

#[derive(Debug)]
struct User {}

impl SomeTrait for User {}

struct Save<T, F>
where
    T: SomeTrait,
    F: Future<Output = Result<(), SomeError>>,
{
    pub validator: Option<Box<dyn Fn(&T) -> F>>,
}

async fn save<T, F>(obj: &T, args: Save<T, F>) -> Result<(), SomeError>
where
    T: SomeTrait,
    F: Future<Output = Result<(), SomeError>>,
{
    if let Some(v) = args.validator {
        (*v)(obj).await?;
    }
    Ok(())
}

#[tokio::test]
async fn test_func() {
    let user = User {};

    save(&user, Save { validator: None }).await;
    save(
        &user,
        Save {
            validator: Some(Box::new(|obj| async {
                println!("input: {:?}", obj);
                Ok(())
            })),
        },
    )
    .await;
}

The error:

error[E0698]: type inside `async` block must be known in this context
  --> test_utils/src/testin.rs:35:17
   |
35 |     save(&user, Save { validator: None }).await;
   |                 ^^^^ cannot infer type for type parameter `F` declared on the struct `Save`
   |
note: the type is part of the `async` block because of this `await`
  --> test_utils/src/testin.rs:35:5
   |
35 |     save(&user, Save { validator: None }).await;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

How can I make the above code work? Is there an alternative implementation without the use of the F generic parameter in the Save struct? I can work with it for now, but might become unwieldy when there are multiple functions in the Save struct.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
rinde
  • 1,181
  • 1
  • 8
  • 20
  • "async closures are unstable" — if you are using unstable features, you **must** note which nightly version you are using. If you aren't using a nightly compiler, you aren't using async closures. Please [edit] your question to clarify. – Shepmaster Jul 07 '20 at 11:39
  • See also [What is the difference between `|_| async move {}` and `async move |_| {}`](https://stackoverflow.com/q/59156473/155423); [What's the difference of lifetime inference between async fn and async closure?](https://stackoverflow.com/q/60751127/155423) – Shepmaster Jul 07 '20 at 11:59
  • See also [How to accept an async function as an argument?](https://stackoverflow.com/q/60717746/155423); [How to indicate that the lifetime of an async function's return value is the same as a parameter?](https://stackoverflow.com/q/60621816/155423). – Shepmaster Jul 07 '20 at 12:00
  • Thank you for your feedback! I'm reading the pages you referred now, and will update my question soon – rinde Jul 07 '20 at 12:03
  • I think my case is different because of the use of an Option, the problem arises only when using `None`. – rinde Jul 07 '20 at 12:24
  • Looks like the code you posted [here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=66c65b1faf1973ba30d0727603ea60bd) does solve my problem, calling `.boxed()` on the async block was the missing link for me. I would accept that code as an answer, and I think it is valuable for others too. – rinde Jul 07 '20 at 12:34

1 Answers1

1

Using BoxFuture

Since you want to hide the type, using a trait object is useful. BoxFuture is well-suited for this, combined with the boxed method to create it:

use futures::{future::BoxFuture, FutureExt};
struct Save<T>
where
    T: SomeTrait,
{
    pub validator: Option<Box<dyn Fn(&T) -> BoxFuture<Result<(), SomeError>>>>,
}
let _ = save(
    &user,
    Save {
        validator: Some(Box::new(|obj| {
            async move {
                println!("input: {:?}", obj);
                Ok(())
            }
            .boxed()
        })),
    },
)
.await;

See also:

Using None with a generic type

The problem here is that the generic type must be known, even if you aren't using it because you've picked None. You could provide a type that fits the constraints (implements Future, Output is a Result<(), SomeError>). Here I use Ready:

type Dummy = futures::future::Ready<Result<(), SomeError>>;

save::<_, Dummy>(&user, Save { validator: None }).await;

Unfortunately, this creates an error I don't know how to solve yet ("borrowed data cannot be stored outside of its closure").

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366