Возврат закрытия из функции, не согласной с определением на месте - PullRequest
1 голос
/ 02 ноября 2019

При попытке реализовать простое приложение веб-сервера с использованием actix-web я столкнулся с явно противоречивым поведением замыканий Rust, которые не знают, как объяснить.

У меня был следующий код:

use actix_web::{web, App, HttpServer};

#[derive(Clone)]
struct Config {
    val1: String,
    val2: String,
    val3: String,
}

fn main() {
    let conf = Config {
        val1: "just".to_string(),
        val2: "some".to_string(),
        val3: "data".to_string(),
    };

    HttpServer::new(move ||
        App::new().configure(create_config(&conf))
    )
        .bind("127.0.0.1:8088")
        .unwrap()
        .run()
        .unwrap();
}

fn create_config<'a>(conf: &'a Config) -> impl FnOnce(&mut web::ServiceConfig) + 'a {
    move |app: &mut web::ServiceConfig| {
        // Have to clone config because web::get().to by definition requires
        // its argument to have static lifetime, which is longer than 'a
        let my_own_conf_clone = conf.clone();
        app.service(
            web::scope("/user")
                .route("", web::get().to(move || get_user(&my_own_conf_clone)))
        );
    }
}

fn get_user(conf: &Config) -> String {
    println!("Config {} is {} here!", conf.val3, conf.val1);
    "User McUser".to_string()
}

Этот код работает. Обратите внимание на закрытие, которое я передаю web::get().to. Я использовал его, чтобы передать объект Config до get_user и по-прежнему представлять web::get().to с функцией, которая не имеет аргументов, как это требуется. На этом этапе я решил перенести генерацию замыканий в отдельную функцию:

fn create_config<'a>(conf: &'a Config) -> impl FnOnce(&mut web::ServiceConfig) + 'a {
    move |app: &mut web::ServiceConfig| {
        app.service(
            web::scope("/user")
                .route("", web::get().to(gen_get_user(conf)))
        );
    }
}

fn gen_get_user(conf: &Config) -> impl Fn() -> String {
    let my_own_conf_clone = conf.clone();
    move || get_user(&my_own_conf_clone)
}

fn get_user(conf: &Config) -> String {
    println!("Config {} is {} here!", conf.val3, conf.val1);
    "User McUser".to_string()
}

Этот код не может быть скомпилирован со следующей ошибкой:

error[E0277]: the trait bound `impl std::ops::Fn<()>: actix_web::handler::Factory<_, _>` is not satisfied
  --> src/main.rs:30:39
   |
30 |       .route("", web::get().to(gen_get_user(conf)))
   |                             ^^ the trait `actix_web::handler::Factory<_, _>` is not implemented for `impl std::ops::Fn<()>`

Почему происходит сбой во второйдело, а не в первом? Почему черта Factory была удовлетворена в первом случае, но не во втором? Может быть, это вина фабрики (ее источники здесь )? Есть ли другой способ вернуть закрытие, которое будет работать в этой ситуации? Любые другие подходы, которые вы могли бы предложить? (Обратите внимание, что Factory не является общедоступным, поэтому я не могу реализовать его непосредственно сам)

Если вы хотите дурачиться с кодом, он у меня здесь: https://github.com/yanivmo/rust-closure-experiments Обратите внимание, что вы можете перемещаться междуКоммит фиксирует код в рабочем или ошибочном состоянии.

1 Ответ

1 голос
/ 02 ноября 2019

Затем, используя impl Trait в качестве возвращаемого типа, вся другая информация о типе, кроме того, что это значение реализует Trait, стирается.

В этом конкретном случае замыкание move || get_user(&my_own_conf_clone) реализует Fn() -> String и Clone, нопосле возврата Clone стираются.

Но поскольку Factory реализовано для Fn() -> String + Clone, а не для Fn() -> String, возвращаемое значение больше не реализует фабрику.

Это можно исправить, изменивgen_get_user до

fn gen_get_user(conf: &Config) -> impl Fn() -> String + Clone{
    let my_own_conf_clone = conf.clone();
    move || get_user(&my_own_conf_clone)
}
...