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

大卫·萨克斯坦

问题

我将 Rust 与 vscode 和“Rust and Friends v1.0.0”引入的插件一起使用。

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

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

由于我是 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 支持 fluent API,因此可以链接所有服务定义,但我更愿意为我添加的每个处理程序范围提取一个函数。

这就是为什么我最初将单个 fluent 调用拆分为两个单独的任务。

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

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

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

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

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

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

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

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

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

从例子中学到

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

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

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

kmdreko

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

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像这样使用参数和返回类型:

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

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

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

然后,您可以选择创建一个 supertrait 以减少调用所需的样板.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());

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章