Реализация FnOnce недостаточно общая в async fn

#rust

#Ржавчина

Вопрос:

Следующий код (игровая площадка) не удается скомпилировать:

 async fn wrapper<F, Fut>(func: F)
where
    F: FnOnce(amp;i32) -> Fut,
    Fut: Future<Output = ()>,
{
    let i = 5;
    func(amp;i).await;
}

async fn myfunc(_: amp;i32) {}

fn main() {
    wrapper(myfunc);
}
  

Сообщение об ошибке:

 error: implementation of `std::ops::FnOnce` is not general enough
   --> src/main.rs:15:5
    |
15  |       wrapper(myfunc);
    |       ^^^^^^^ implementation of `std::ops::FnOnce` is not general enough
    |
    = note: `std::ops::FnOnce<(amp;'0 i32,)>` would have to be implemented for the type `for<'_> fn(amp;i32) -> impl std::future::Future {myfunc}`, for some specific lifetime `'0`...
    = note: ...but `std::ops::FnOnce<(amp;i32,)>` is actually implemented for the type `for<'_> fn(amp;i32) -> impl std::future::Future {myfunc}`
  

Я нашел несколько похожих элементов, в которых упоминаются границы признаков с более высоким рейтингом (например, эта проблема с Rust и эта), и попытался поэкспериментировать с for<'a> дополнительными ограничениями времени жизни, но ошибка мне все еще не ясна.

Я не понимаю, почему компилятор не может определить время жизни, тем более что более простая версия без фьючерсов компилируется просто отлично:

 fn myfunc(_: amp;i32) {}

fn wrapper<F: FnOnce(amp;i32)>(func: F) {
    let i = 5;
    func(amp;i);
}

fn main() {
    wrapper(myfunc);
}
  

В чем здесь проблема? Есть ли обходной путь, который может сделать эту компиляцию, например, с дополнительными ограничениями?

Ответ №1:

Исходя из того, что объясняется в предоставленной вами проблеме, для функций, ожидаемых вашей оболочкой, может быть создан определенный признак.

Будучи сам новичком в Rust, я точно не знаю, почему в вашей оригинальной версии компилятор жалуется на несоответствие типов, которые фактически эквивалентны в отображаемом сообщении об ошибке (это еще более очевидно, когда у нас были явные времена жизни; отображаемые типы идеально совпадают!). Я просто предполагаю, что это связано с тем фактом, что точный тип результата скрыт за impl обозначением, а фактическая разница заключается в этой скрытой части.

Действительно, трудно выразить, что любое время жизни for<'r> должно учитываться как для amp; параметра, так и для Future результата, потому что они появляются в двух разных ограничениях where предложения. Насколько я понимаю (не так много …), учет явного времени жизни для всего признака делает for<'r> обозначения согласованными как для amp; параметра, так Future и для результата, потому что все это находится в одном и том же ограничении where предложения.

Поскольку мы имеем дело со временем жизни, я решил заменить копируемое i32 на не копируемое String , чтобы предотвратить любые неожиданные упрощения.

Как указано в приведенной вами ссылке, это решение, похоже, работает только с функциями, а не с замыканиями.

 use std::future::Future;

async fn wrapper<F>(func: F)
where
    F: for<'r> Wrapped<'r>,
{
    let s = String::from("WRAPPED");
    func.call_once(amp;s).await;
}

trait Wrapped<'a> {
    type Res: Future<Output = ()>;
    fn call_once(
        self,
        s: amp;'a String,
    ) -> Self::Res;
}

impl<'a, F, FutRes> Wrapped<'a> for F
where
    F: FnOnce(amp;'a String) -> FutRes,
    FutRes: Future<Output = ()>   'a,
{
    type Res = FutRes;
    fn call_once(
        self,
        s: amp;'a String,
    ) -> Self::Res {
        self(s)
    }
}

async fn call_myfunc() {
    let s = String::from("HARDCODED");
    myfunc(amp;s).await;
}

async fn myfunc(arg: amp;String) {
    println!("arg={}", arg);
}

fn main() {
    println!("~~~~ hardcoded call ~~~~");
    let f = call_myfunc();
    futures::executor::block_on(f);
    println!("~~~~ use wrapper() ~~~~");
    let f = wrapper(myfunc);
    futures::executor::block_on(f);
}