Как создать обертку, которая принимает одну асинхронную функцию и возвращает другую асинхронную функцию в Rust?

В функциональном программировании существует общая закономерность: вы берете функцию, используете ее и возвращаете другую функцию (часто с той же сигнатурой). В Python этот шаблон настолько распространен, что использует особый синтаксис декоратора @.

Я пытаюсь реализовать этот подход в Rust, но на нетривиальных примерах у меня ничего не получается.

Спасибо за этот ответ, этот тривиальный синхронный пример работает:

fn wrapper<T, V, F>(f: F) -> impl Fn(T) -> V where
    F: Fn(T) -> V {
    move |x| f(x)
}

fn my_func_1(x: u16) -> u16 {
    x * 2
}

fn my_func_2(x: Vec<i32>) -> i32 {
    x.iter().sum()
}

fn main() {
    let res = wrapper(my_func_1)(42);
    let res = wrapper(my_func_2)(vec![1,2,3]);
}

Но в асинхронном контексте все становится запутанным! В этом случае мне не удалось реализовать ту же функциональность. Но благодаря этому ответу я принял такое тривиальное решение:

use std::future::Future;

fn wrapper<T, V, F, Fut>(f: F) -> impl Fn(T) -> Fut where
    F: Fn(T) -> Fut,
    Fut: Future<Output=V> {
    f
}

async fn my_func_1(x: u16) -> u16 {
    x * 2
}

async fn my_func_2(x: Vec<i32>) -> i32 {
    x.iter().sum()
}


#[tokio::main]
async fn main() {
    let res = wrapper(my_func_1)(42).await;
    println!("my_func_1: res = {:?}", res);
    let res = wrapper(my_func_2)(vec![1,2,3]).await;
    println!("my_func_2: res = {:?}", res);
}

По сравнению с синхронной реализацией в оболочке async я не могу создать новую функцию для возврата. Что-то вроде этого:

fn wrapper<T, V, F, Fut>(f: F) -> impl Fn(T) -> Fut where
    F: Fn(T) -> Fut,
    Fut: Future<Output=V> {
    move |x| async { f(x) }
}

завершается с ошибкой:

error[E0308]: mismatched types
 --> src/main.rs:7:14
  |
4 | fn wrapper<T, V, F, Fut>(f: F) -> impl Fn(T) -> Fut where
  |                     --- expected this type parameter
...
7 |     move |x| async { f(x) }
  |              ^^^^^^^^^^^^^^ expected type parameter `Fut`, found `async` block
  |
  = note: expected type parameter `Fut`
              found `async` block `{async block@src/main.rs:7:14: 7:28}`
  = help: every closure has a distinct type and so could not always match the caller-chosen type of parameter `Fut`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `rust_test` (bin "rust_test") due to 1 previous error

Какие-либо предложения!?

Пожалуйста, опубликуйте полную ошибку из cargo check, а не из вашей IDE.

Chayim Friedman 01.08.2024 22:11
-> impl Fn(T) -> Fut означает функцию, которая при задании T возвращает Fut; примечание Fut здесь является фиксированным типом. Мне не ясно, хотите ли вы, чтобы это оставалось таким (и в этом случае ошибка имеет смысл, поскольку вы не возвращаете Fut из замыкания), или если вы хотите, чтобы это было другое будущее, чем Fut, что тоже случается выведите V и даже можете использовать F при этом.
GManNickG 01.08.2024 22:40

На самом деле вам нужно что-то вроде -> impl Fn(T) -> impl Future<Output = V>, но этот синтаксис (в настоящее время) запрещен.

cdhowie 02.08.2024 00:00
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
3
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы помещаете Future в другой асинхронный блок, поэтому эта функция:

fn wrapper<T, V, F, Fut>(f: F) -> impl Fn(T) -> ?
where
    F: Fn(T) -> Fut,
    Fut: Future<Output = V>,
{
    move |x| async { f(x) }
}

нужно будет вызывать с двумя await:

let res = wrapper(my_func_1)(42).await.await;

Вероятно, это не то, чего вы хотите. Вы либо хотите, чтобы замыкание возвращало Future напрямую:

fn wrapper<T, V, F, Fut>(f: F) -> impl Fn(T) -> Fut
where
    F: Fn(T) -> Fut,
    Fut: Future<Output = V>,
{
    move |x| f(x)
}

или вы хотите дождаться Future и вернуть новый тип Future. К сожалению, вам нужна еще одна черта, чтобы выразить это.

fn wrapper<T, V, F, Fut>(f: F) -> impl FutureFn<T, Fut: Future<Output = V>>
where
    F: Fn(T) -> Fut,
    Fut: Future<Output = V>,
{
    |x| async move { f(x).await }
}

trait FutureFn<T>: FnOnce(T) -> Self::Fut {
    type Fut: Future;
}

impl<F, Fut, T> FutureFn<T> for F
where
    F: FnOnce(T) -> Fut,
    Fut: Future,
{
    type Fut = Fut;
}

Подробнее об этом читайте в разделе Вызов общей асинхронной функции с (изменяемым) заимствованным аргументом. Обратите внимание: когда T включает время жизни, это работает только тогда, когда вы передаете элемент асинхронной функции в wrapper, а не замыкание.

@cdhowie Я определенно скопировал туда не то, оно должно просто вернуться Fut

drewtato 02.08.2024 03:09

Ах, это имеет больше смысла!

cdhowie 02.08.2024 09:30

Спасибо, это делает работу!

Nikolay S. Vasil'ev 02.08.2024 13:08

Другие вопросы по теме