当根据特征定义提取函数的 return 类型时,如何提取 Rust 中的一部分函数?

How can I extract a part of function in Rust when the return type of the extracted function is defined in terms of a trait?

问题

我正在使用 Rust 和 vscode 以及“Rust and Friends”引入的插件 v1.0.0".

我想使用提取函数技术重构一个长函数,但在某些情况下 IDE 无法确定提取函数的 return 类型。

我认为原因是该类型是根据特征描述的,不可能将该类型定义为 return 类型。

由于我是 Rust 的新手,我希望我的评估不准确,我将提供一个示例。

例子

我正在使用回形针箱来设置 REST 服务器。 配置服务器的部分如下所示:

let server = HttpServer::new(move || {
    let app = App::new()
        .wrap(Logger::default())
        .wrap_api()
        .data(pool.clone());

    let app = app.service(
        web::scope(“/api”).service(
            web::scope(“/customers”).service(
                web::resource(“/transactions”)
                    .route(web::get().to(schema_handlers::get_transactions))
                    .route(web::post().to(schema_handlers::add_transaction)),
            ),
        ),
    );
    
    let app = app.service(
        web::scope(“/api”).service(
            web::scope(“/admin”).service(
                web::resource(“/permissions”)
                    .route(web::get().to(schema_handlers::get_permissions))
                    .route(web::post().to(schema_handlers::add_permission)),
            ),
        ),
    );

    app.with_json_spec_at("/api/spec").build()
})
.bind(format!("0.0.0.0:{}", port))?
.run();

paperclip 支持流畅的 API 以便可以链接所有服务定义,但我更愿意为我添加的每个处理程序范围提取一个函数。

这就是为什么我最初将单个流利调用分成两个单独的作业的原因。

下一步是将每个 let app = app.service ( 语句提取到一个函数中。

但要这样做,我需要能够表达 app 的类型,或者至少是公开 service 方法的特征的名称,因为它在这里使用。

在这种情况下,IDE 无法检测类型。

当我使用 The “let” type trick in Rust 和 IDE 中的一些提示时,我得出的结论是类型是:

App<impl ServiceFactory<Config = (), Request = ServiceRequest, Response = ServiceResponse<StreamLog<Body>>, Error = Error, InitError = ()>, StreamLog<Body>>

此类型不能显式用于限定 app 变量,也不能用作提取函数的 return 类型,该类型将替换对 [=14 的赋值的右侧=].

根据编译器错误消息,我了解到类型表达式中存在特征(如 impl 关键字的存在所指示的是此问题的原因。

另一个问题是这种类型说明非常冗长。

我可以通过类型别名来解决冗长的问题,但是编译器抱怨说 impl 在类型别名中不稳定,这在我看来就像归结为同样的问题。

从示例中学习

在我看来,有些情况下类型定义明确并且可以由编译器推断,但是因为它们包含特征定义,所以不能(轻松)显式编写它们,因此 extract function重构的方法并不总是可行的。

在我看来,这似乎是该语言的一个重要限制。

今天有什么方法可以提取函数(无需等待 Rust 中的特征别名)?

我假设您希望通过重构来改变这一点:

let app = app.service(
    web::scope("/api").service(
        web::scope("/customers").service(
            web::resource("/transactions")
                .route(web::get().to(schema_handlers::get_transactions))
                .route(web::post().to(schema_handlers::add_transaction)),
        ),
    ),
);

变成这样的东西:

fn add_transaction_routes(app: App) -> App {
    app.service(
        web::scope("/api").service(
            web::scope("/customers").service(
                web::resource("/transactions")
                    .route(web::get().to(schema_handlers::get_transactions))
                    .route(web::post().to(schema_handlers::add_transaction)),
            ),
        ),
    )
}

let app = add_transaction_routes(app);

当然这不起作用,因为 App 是通用的并且不完整。您可以使用 impl Trait 作为参数和 return 类型,如下所示:

fn add_transaction_routes(
    app: App<impl ServiceFactory<...>, Body>,
) -> App<impl ServiceFactory<...>, Body> {

但我认为这是轻微的误用。虽然 本身可能并不正确 ,但 impl Trait 是单独推导出来的。函数签名表示传入的 app 可能与 return 的类型不同,但 .service() 实际上是 return 和 Self。所以把它变成一个简单的通用函数会更合适:

fn add_transaction_routes<T>(app: App<T, Body>) -> App<T, Body>
where
    T: ServiceFactory<...>,
{

然后您可以选择制作超级特征以减少调用 .service():

所需的样板文件
ServiceFactory<
    ServiceRequest,
    Config = (),
    Response = ServiceResponse<Body>,
    Error = Error,
    InitError = (),
>

但这一切开始变得混乱。虽然这是可能的,但很明显,这种类型的组织并不是专门为之设计的。相反,我建议您的重构围绕提供服务而不是将它们添加到 App。我认为这更清楚:

fn transaction_routes() -> impl HttpServiceFactory {
    web::scope("/api").service(
        web::scope("/customers").service(
            web::resource("/transactions")
                .route(web::get().to(schema_handlers::get_transactions))
                .route(web::post().to(schema_handlers::add_transaction)),
        ),
    )
}

let app = app.service(transaction_routes());