How to return an async function in a generic struct

I have the following struct:

struct ExecReturnType<T, F, C>
where
    T: 'static,
    F: Future<Output = T> + 'static,
    C: FnMut() -> F,
{
    state: Signal<CommandState>,
    fun:   C,
}

And this is how I initialize and return it:

pub fn use_exec_with_duration<T, F>(
    mut future: impl FnMut() -> F + 'static,
) -> ExecReturnType<T, impl Future<Output = T>, impl FnMut() -> impl Future<Output = T>>
where
    T: 'static,
    F: Future<Output = T> + 'static,
{
    let mut state = use_signal(CommandState::default);
    // ...
    ExecReturnType {
        state,
        fun: move || {
            let res = future();
            *state.write() = CommandState::Submitting;
            timeout.action(());
            async move {
                let res = res.await;
                *state.write() = CommandState::None;
                res
            }
        },
    }
}

But the compiler tells me that different impl Trait results in different obaque types. I tried different approaches, but none seems to work, how should I solve this?

you need a different trait for the opaque type, the syntax of FnMut() does not support such case, you can try AsyncFnMut() in the standard library, but it has its own drawbacks, most notably, the associated Future is not stable (yet?) so you cannot add trait bounds for them.

if the standard library doesn't fit your use case and you don't want to roll your own custom trait, you can try the async-fn-traits crate.

here's an example using the standard library:

struct ExecReturnType<T, C>
where
    T: 'static,
    C: AsyncFnMut() -> T,
{
    state: Signal<CommandState>,
    fun:   C,
}

pub fn use_exec_with_duration<T, F>(
    mut future: impl FnMut() -> F + 'static,
) -> ExecReturnType<T, impl AsyncFnMut() -> T>
where
    T: 'static,
    F: Future<Output = T> + 'static,
{
    //...
}
2 Likes

Much appreciated, AsyncFnMut was indeed the solution.
Just in case this is an xy problem, I want to explain the bigger picture.

I'm using dioxus and the way to call async functions is with

pub fn use_resource<T, F>(mut future: impl FnMut() -> F + 'static) -> Resource<T>
where
    T: 'static,
    F: Future<Output = T> + 'static

The way Resource<T> is used is irrelevant, but the argument mut future: impl FnMut() -> F + 'static is.
What I want to do, is to have a function that wraps whatever future I want to use to the use_resource function so that it returns a Signal<CommandState> and automatically handle its state.

#[derive(Default, Debug, Clone)]
pub enum CommandState {
    #[default]
    None,
    Submitting, // the command has started
    Delayed, // 500ms have passed executing the command
}

This forced me to do the following:

    let res = hooks::command::use_exec_with_duration(services::db::create);
    let cmd_state = res.state;
    let fun = std::rc::Rc::new(std::cell::RefCell::new(res.fun));
    let resource = use_resource(move || {
        let fun = fun.clone();
        async move { fun.borrow_mut()().await }
    });

Is this the right way?

I'm not familiar with dioxus, but I think you are on the right track.

one of the main use cases for the AsyncFn falimiy of traits is to create generic wrappers for async function, similar to how the Fn traits are used to create generic wrappers for closures, so your code fits this scenario well.

at its current stage, the major draw back is you cannot add additional trait bounds on the associated Future type, which can be a problem for multi-threaded async runtimes like tokio, because there's no way (in stable rust) to specify the Send bounds which is required to spawn tasks. it seems dioxus doesn't require Send for the future, so it's not a problem in this case.

however, this restriction is not a problem if you use a custom trait like the one from the async-fn-traits crate, you just lose convenience of the syntax sugar for the AsyncFn traits of the standard library, not really a big deal.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.