Вернуть функцию asyn c из функции в Rust - PullRequest
3 голосов
/ 12 апреля 2020

Часть 1: Какой должна быть подпись функции, возвращающей асинхронную c функцию?

pub async fn some_async_func(arg: &str) {}

// What should be sig here?
pub fn higher_order_func(action: &str) -> ???
{
    some_async_func
}

Часть 2: Каким должен быть сигнал, если он основан на параметре действия, upper_order_fun c должен был вернуть либо async_func1, либо async_func2.

Я также заинтересован в изучении компромисса производительности, если есть несколько решений. Обратите внимание, что я хотел бы вернуть саму функцию в качестве указателя fn или признака Fn *, а не в результате ее вызова.

1 Ответ

8 голосов
/ 12 апреля 2020

Возврат функции

Возврат фактического указателя функции требует выделения кучи и обертки:

use std::future::Future;
use std::pin::Pin;

pub async fn some_async_func(arg: &str) {}

pub fn some_async_func_wrapper<'a>(arg: &'a str)
    -> Pin<Box<dyn Future<Output=()> + 'a>>
{
    Box::pin(some_async_func(arg))
}

pub fn higher_order_func<'a>(action: &str)
    -> fn(&'a str) -> Pin<Box<dyn Future<Output=()> + 'a>>
{
    some_async_func_wrapper
}

Почему бокс? higher_order_func должен иметь конкретный тип возврата, который является указателем на функцию. Указанная функция также должна иметь конкретный тип возврата, что невозможно для функции async, поскольку она возвращает непрозрачный тип. Теоретически, можно было бы написать тип возвращаемого значения как fn(&'a str) -> impl Future<Output=()> + 'a, но это потребовало бы гораздо больше догадок от компилятора и в настоящее время не поддерживается.

Если вы в порядке с Fn вместо fn, вы можете избавиться от оболочки:

pub async fn some_async_func(arg: &str) {}

pub fn higher_order_func<'a>(action: &str)
    -> impl Fn(&'a str) -> Pin<Box<dyn Future<Output=()> + 'a>>
{
    |arg: &'a str| {
        Box::pin(some_async_func(arg))
    }
}

Чтобы вернуть другую функцию, основанную на значении action, вам нужно будет поместить само замыкание в коробку, что является еще одним выделением кучи:

pub async fn some_async_func_one(arg: &str) {}
pub async fn some_async_func_two(arg: &str) {}

pub fn higher_order_func<'a>(action: &str)
    -> Box<dyn Fn(&'a str) -> Pin<Box<dyn Future<Output=()> + 'a>>>
{
    if action.starts_with("one") {
        Box::new(|arg: &'a str| {
            Box::pin(some_async_func_one(arg))
        })
    } else {
        Box::new(|arg: &'a str| {
            Box::pin(some_async_func_two(arg))
        })
    }
}

Альтернатива: возвращение будущего

Чтобы упростить вещи, рассмотрите возможность возврата самого будущего вместо указателя функции. Это практически то же самое, но гораздо приятнее и не требует выделения кучи:

pub async fn some_async_func(arg: &str) {}

pub fn higher_order_func_future<'a>(action: &str, arg: &'a str)
    -> impl Future<Output=()> + 'a
{
    some_async_func(arg)
}

Может выглядеть, когда вызывается higher_order_func_future, выполняется some_async_func - но это не дело. Из-за того, что работают функции asyn c, при вызове some_async_func, пользовательский код не выполняется . Вызов функции возвращает Future: фактическое тело функции будет выполнено только , когда кто-то ожидает возвращенного будущего.

Вы можете использовать новую функцию почти так же, как и предыдущую функцию. :

// With higher order function returning function pointer
async fn my_function() {
    let action = "one";
    let arg = "hello";
    higher_order_func(action)(arg).await;
}

// With higher order function returning future
async fn my_function() {
    let action = "one";
    let arg = "hello";
    higher_order_func_future(action, arg).await;
}

Еще раз обратите внимание, что в обоих случаях фактическое some_async_func тело выполняется только , когда ожидается будущее.

Если вы хотели чтобы иметь возможность вызывать различные асин c функции, основанные на значении action, вам нужно снова создать бокс:

pub async fn some_async_func_one(arg: &str) {}
pub async fn some_async_func_two(arg: &str) {}

pub fn higher_order_func_future<'a>(action: &str, arg: &'a str)
    -> Pin<Box<dyn Future<Output=()> + 'a>>
{
    if action.starts_with("one") {
        Box::pin(some_async_func_one(arg))
    } else {
        Box::pin(some_async_func_two(arg))
    }
}

Тем не менее, это всего лишь одна куча, поэтому я настоятельно рекомендую возвращать будущее. Единственный сценарий, который я могу себе представить, где предыдущее решение лучше, - это когда вы хотите сохранить коробочное закрытие где-нибудь и использовать его много раз. В этом случае чрезмерное распределение происходит только один раз, и вы экономите некоторое время ЦП, отправляя вызов на основе action только один раз - при закрытии.

...