Multiple async functions as parameters

Hello.

I'm trying to pass functions as callbacks in rust, everything looks fine with normal functions.

#[derive(Debug, PartialEq)]
pub enum ExplainResult {
    Integer(i32),
    Float(f64),
}

fn people_explain_one_number(value: i32) -> ExplainResult {
    ExplainResult::Integer(value)
}

fn people_explain_two_numbers(value: i32, value2: i32) -> ExplainResult {
    ExplainResult::Float(value as f64 / value2 as f64)
}

fn dog_explain_one_number(value: i32) -> ExplainResult {
    ExplainResult::Integer(value - 1)
}

fn dog_explain_two_numbers(value: i32, value2: i32) -> ExplainResult {
    ExplainResult::Float(value as f64 / value2 as f64 - 1.0)
}

pub fn explain_by_people(key: &str) -> ExplainResult {
    common_explain(key, people_explain_one_number, people_explain_two_numbers)
}

pub fn explain_by_dog(key: &str) -> ExplainResult {
    common_explain(key, dog_explain_one_number, dog_explain_two_numbers)
}

fn common_explain(
    key: &str,
    explain_one_number: impl Fn(i32) -> ExplainResult,
    explain_two_number: impl Fn(i32, i32) -> ExplainResult,
) -> ExplainResult {
    match key {
        "what is 1" => explain_one_number(1),
        "what is 3 divide by 2" => explain_two_number(3, 2),
        _ => ExplainResult::Integer(0),
    }
}

But I'm struggling to make the impl functions async.

  1. when I passed only one function r, it works well.
use std::future::Future;

fn common_explain<Fut>(key: &str, explain_one_number: impl Fn(i32) -> Fut) -> Fut
where
    Fut: Future<Output = ExplainResult>,
{
    match key {
        "what is 1" => explain_one_number(1),
        _ => explain_one_number(0),
    }
}

async fn people_explain_one_number(value: i32) -> ExplainResult {
    ExplainResult::Integer(value)
}

pub async fn explain_by_people(key: &str) -> ExplainResult {
    common_explain(key, people_explain_one_number).await
}

2.when I passed two functions as parameters, seems that the where syntax does not work (Playground).

According to this thread, I kind of understand why. (because of the where syntax is something like impl Trait, which actually gives no explicit type, so the compiler thinks the two functions return different types? Please correct me if there are problems with my understanding.)

fn common_explain<Fut>(
    key: &str,
    explain_one_number: impl Fn(i32) -> Fut,
    explain_two_number: impl Fn(i32, i32) -> Fut,
) -> Fut
where
    Fut: Future<Output = ExplainResult>,
{
    match key {
        "what is 1" => explain_one_number(1),
        "what is 3 divide by 2" => explain_two_number(3, 2),
        _ => explain_one_number(0),
    }
}
  1. so I'm trying to make the return type explicit. I have few experience in rust, in my experience, I should work with Box and dyn. I find this post rust - How can one await a result of a boxed future? - Stack Overflow to use Future with Box, but the code (Playground) produces some errors, and I have no clue to handle this.
fn common_explain(
    key: &str,
    explain_one_number: impl Fn(i32) -> dyn Future<Output = ExplainResult>,
    explain_two_number: impl Fn(i32, i32) -> dyn Future<Output = ExplainResult>,
) -> Pin<Box<dyn Future<Output = ExplainResult>>> {
    let explain_one_number = |value: i32| Box::pin(explain_one_number(value));
    let explain_two_number = |value: i32, value2: i32| Box::pin(explain_two_number(value, value2));

    match key {
        "what is 1" => explain_one_number(1),
        "what is 3 divide by 2" => explain_two_number(3, 2),
        _ => explain_one_number(0),
    }
}

here is a part of the error.

error[E0277]: the size for values of type `dyn Future<Output = ExplainResult>` cannot be known at compilation time
  --> src/lib.rs:38:52
   |
38 |     let explain_one_number = |value: i32| Box::pin(explain_one_number(value));
   |                                           -------- ^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |                                           |
   |                                           required by a bound introduced by this call
   |
   = help: the trait `Sized` is not implemented for `dyn Future<Output = ExplainResult>`
note: required by a bound in `Box::<T>::pin`

Am I moving in the right direction to solve this problem?

The problem in your original async example is that you're requiring the actual future each function returns to be the same type. There are some cases where that might work, but it won't ever let you use two different async functions since each async function returns a custom compiler generated future type.

You can fix it by simply using two type parameters and making your function async [1].

Playground

use std::future::Future;

#[derive(Debug, PartialEq)]
pub enum ExplainResult {
    Integer(i32),
    Float(f64),
}

async fn common_explain<Fut, FutTwo>(
    key: &str,
    explain_one_number: impl Fn(i32) -> Fut,
    explain_two_number: impl Fn(i32, i32) -> FutTwo,
) -> ExplainResult
where
    Fut: Future<Output = ExplainResult>,
    FutTwo: Future<Output = ExplainResult>,
{
    match key {
        "what is 1" => explain_one_number(1).await,
        "what is 3 divide by 2" => explain_two_number(3, 2).await,
        _ => explain_one_number(0).await,
    }
}

async fn people_explain_one_number(value: i32) -> ExplainResult {
    ExplainResult::Integer(value)
}

async fn people_explain_two_numbers(value: i32, value2: i32) -> ExplainResult {
    ExplainResult::Float(value as f64 / value2 as f64)
}

pub async fn explain_by_people(key: &str) -> ExplainResult {
    common_explain(key, people_explain_one_number, people_explain_two_numbers).await
}

You could also go the Box<dyn Future> route. It looks like your problem in your dyn example is you have dyn Future in your common_explain signature directly. But you can't return a raw trait object like that[2]. You need it to be boxed before the function is called. You could also use type parameters to solve that like in the first playground above. For variety here I'll just box them before passing them to common_explain.

Playground

use std::{future::Future, pin::Pin};

#[derive(Debug, PartialEq)]
pub enum ExplainResult {
    Integer(i32),
    Float(f64),
}

fn common_explain(
    key: &str,
    explain_one_number: impl Fn(i32) -> Pin<Box<dyn Future<Output = ExplainResult>>>,
    explain_two_number: impl Fn(i32, i32) -> Pin<Box<dyn Future<Output = ExplainResult>>>,
) -> Pin<Box<dyn Future<Output = ExplainResult>>> {
    match key {
        "what is 1" => explain_one_number(1),
        "what is 3 divide by 2" => explain_two_number(3, 2),
        _ => explain_one_number(0),
    }
}

async fn people_explain_one_number(value: i32) -> ExplainResult {
    ExplainResult::Integer(value)
}

async fn people_explain_two_numbers(value: i32, value2: i32) -> ExplainResult {
    ExplainResult::Float(value as f64 / value2 as f64)
}

pub async fn explain_by_people(key: &str) -> ExplainResult {
    common_explain(
        key,
        |v| Box::pin(people_explain_one_number(v)),
        |a, b| Box::pin(people_explain_two_numbers(a, b)),
    )
    .await
}

  1. You could also use a future combinator of some sort instead of making common_explain async, but I'm not sure that's simpler here ↩︎

  2. In current Rust, at least ↩︎

1 Like

Thanks a lot, learned more about Rust. :smiling_face_with_three_hearts:

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.