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

Dav*_*ein 2 refactoring traits rust type-alias

问题

\n

我将 Rust 与 vscode 以及“Rust and Friends\nv1.0.0”引入的插件一起使用。

\n

我想使用提取函数技术重构一个长函数,但在某些情况下,IDE 无法计算出提取函数的返回类型。

\n

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

\n

由于我是 Rust 新手,我预计我的评估不准确,因此我将提供一个示例。

\n

例子

\n

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

\n
let server = HttpServer::new(move || {\n    let app = App::new()\n        .wrap(Logger::default())\n        .wrap_api()\n        .data(pool.clone());\n\n    let app = app.service(\n        web::scope(\xe2\x80\x9c/api\xe2\x80\x9d).service(\n            web::scope(\xe2\x80\x9c/customers\xe2\x80\x9d).service(\n                web::resource(\xe2\x80\x9c/transactions\xe2\x80\x9d)\n                    .route(web::get().to(schema_handlers::get_transactions))\n                    .route(web::post().to(schema_handlers::add_transaction)),\n            ),\n        ),\n    );\n    \n    let app = app.service(\n        web::scope(\xe2\x80\x9c/api\xe2\x80\x9d).service(\n            web::scope(\xe2\x80\x9c/admin\xe2\x80\x9d).service(\n                web::resource(\xe2\x80\x9c/permissions\xe2\x80\x9d)\n                    .route(web::get().to(schema_handlers::get_permissions))\n                    .route(web::post().to(schema_handlers::add_permission)),\n            ),\n        ),\n    );\n\n    app.with_json_spec_at("/api/spec").build()\n})\n.bind(format!("0.0.0.0:{}", port))?\n.run();\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n

但要做到这一点,我需要能够表达app公开该方法的特征的类型或至少是其名称service

\n

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

\n

当我使用Rust 中的 \xe2\x80\x9clet\xe2\x80\x9d 类型技巧以及 IDE 中的一些提示时,我得出的结论是该类型是:

\n
App<impl ServiceFactory<Config = (), Request = ServiceRequest, Response = ServiceResponse<StreamLog<Body>>, Error = Error, InitError = ()>, StreamLog<Body>>\n
Run Code Online (Sandbox Code Playgroud)\n

该类型不能显式地用于限定app变量,也不能用作提取函数的返回类型来替换赋值的右侧app

\n

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

\n

另一个问题是这种类型规范非常长且冗长。

\n

我可以通过类型别名来解决冗长的问题,但编译会抱怨impl类型别名不稳定,这在我看来归结为同一问题。

\n

从例子中得知

\n

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

\n

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

\n

现在有没有一种方法可以提取函数(无需等待 Rust 中的特征别名)?

\n

kmd*_*eko 6

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

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)),
        ),
    ),
);
Run Code Online (Sandbox Code Playgroud)

变成这样的东西:

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);
Run Code Online (Sandbox Code Playgroud)

当然,这是行不通的,因为它App是通用的并且不完整。您可以impl Trait像这样使用参数和返回类型:

fn add_transaction_routes(
    app: App<impl ServiceFactory<...>, Body>,
) -> App<impl ServiceFactory<...>, Body> {
Run Code Online (Sandbox Code Playgroud)

但我认为这是轻微的误用。虽然它本身可能不正确,但impl Traits 是单独推导的。函数签名表明app传入的 可能与返回的类型不同,但.service()实际上返回的是Self。因此,将其设为一个简单的通用函数会更合适:

fn add_transaction_routes<T>(app: App<T, Body>) -> App<T, Body>
where
    T: ServiceFactory<...>,
{
Run Code Online (Sandbox Code Playgroud)

然后,您可以选择创建一个超级特征来减少调用所需的样板.service()

ServiceFactory<
    ServiceRequest,
    Config = (),
    Response = ServiceResponse<Body>,
    Error = Error,
    InitError = (),
>
Run Code Online (Sandbox Code Playgroud)

但这一切开始变得混乱。虽然有可能,但很明显这种类型的组织并不是专门设计的。相反,我建议您的重构是围绕制作服务而不是围绕将它们添加到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());
Run Code Online (Sandbox Code Playgroud)