英文:
decltype, dyn, impl traits, and how to declare the return type of a function when refactoring
问题
我理解你的问题,你想将特定的代码块封装成一个函数,并且在另一个文件中使用。以下是你的代码的翻译部分:
我是一个Rust新手,但是我是一个有经验的程序员,从C++的角度来看待Rust,这可能解释了我的困惑。
我有一个核心的、非常简单的问题要解决。然而,在Rust中似乎最基本的事情已经让我陷入了麻烦。
我有一段代码:
~~~
pub fn my_application(cfg: &mut web::ServiceConfig) {
let auth = HttpAuthentication::bearer(validate_bearer);
...
~~~
我想要重构它。不再使用'actix'的HttpAuthentication::bearer中间件,而是使用我自己的自定义中间件。
所以,我只是想把等号后面的内容放到另一个文件的一个函数中。
这有多难呢?
我尝试了显而易见的方法:
在一个新文件中:
~~~
// 不会重复'use'语句在剩下的代码中,但为了演示包含一次)
use actix_web::dev::ServiceRequest;
use actix_web::Error;
use actix_web_httpauth::extractors::bearer::BearerAuth;
use actix_web_httpauth::middleware::HttpAuthentication;
use core::future::Future;
use crate::server::auth::common::validate_bearer;
// 在C++中,你可能会说auto,或者类似的东西
pub fn create_auth_middleware_1()
{
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
~~~
这失败了:
~~~
^^^^ 期望`()`,找到`HttpAuthentication<BearerAuth, ...>`
~~~
好的 - 所以Rust对于函数声明没有auto或类型推导(或者我错过了它的语法)。好吧。
接下来,我尝试了'泛型':
```rust
pub fn create_auth_middleware_2<T>() -> T
{
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
这导致了:
^^^^ 期望类型参数`T`,找到`HttpAuthentication<BearerAuth, ...>`
接下来 - 尝试了一系列关于T的'where'子句,以指定它,灵感来自于service 'wrap'函数上的声明 - 一种非常复杂的类型声明。例如:
// 一次添加'use'语句,但保留其他需要它的示例...
use actix_service::boxed::{BoxService};
use actix_web::body::MessageBody;
use actix_web::dev::{Payload, Service, ServiceResponse, Transform};
use actix_web::http::header::Header;
use actix_web::{FromRequest, HttpRequest};
pub fn create_auth_middleware_3<T>() -> T
where T: Transform<S, ServiceRequest>,
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> {
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
也许编译器这次给了一些提示!
auth
| ^^^^ 期望类型参数`T`,找到`HttpAuthentication<BearerAuth, fn(..., ...) -> ... {validate_bearer}>`
|
= 注释:期望类型参数`T`
找到结构体`HttpAuthentication<BearerAuth, fn(..., ...) -> ... {validate_bearer}>`
完整的类型名称已写入....
所以我尝试了编译器写入那个文件的实际类型:
pub fn create_auth_middleware_4() -> HttpAuthentication<BearerAuth, fn(ServiceRequest, BearerAuth) -> impl futures::Future<Output = std::result::Result<ServiceRequest, actix_web::Error>> {validate_bearer}> {
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
导致:
error[E0747]: constant provided when a type was expected
--> src/server/auth/middleware.rs:37:69
|
37 | ...h, fn(ServiceRequest, BearerAuth) -> impl futures::Future<Output = std::result::Result<ServiceRequest, actix_web::Error>> {validate_bearer}...
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
好吧 - 也许Rust不喜欢作为类型的一部分显式的{validate_bearer}
。
英文:
I'm a Rust newbie, but programming veteran approaching Rust from a C++ point of view/background, so that may explain much of my confusion.
I have at its heart, a mind-numbingly simple problem to solve. And yet what seems the most basic of things in rust has run me into a rats nest of trouble.
I have a bit of code:
pub fn my_application(cfg: &mut web::ServiceConfig) {
let auth = HttpAuthentication::bearer(validate_bearer);
...
which I wish to refactor. Instead of using the 'actix' HttpAuthentication::bearer middleware, I want to use my own custom middleware.
So all I'm trying to do is take the 'let auth = ...' line and put the stuff after the equal sign into a function, in another file.
How hard could that be?
So I tried the obvious:
in a new file:
// WONT repeat 'use' statements in rest, but included once in case you try to compile)
use actix_web::dev::ServiceRequest;
use actix_web::Error;
use actix_web_httpauth::extractors::bearer::BearerAuth;
use actix_web_httpauth::middleware::HttpAuthentication;
use core::future::Future;
use crate::server::auth::common::validate_bearer;
// in C++, you would might say auto, or some such
pub fn create_auth_middleware_1()
{
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
That failed:
^^^^ expected `()`, found `HttpAuthentication<BearerAuth, ...>`
OK - so Rust has no auto or type deduction for function declarations (or I've missed the syntax for that). Fine.
Next I tried 'generics'
pub fn create_auth_middleware_2<T>() -> T
{
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
which results in:
^^^^ expected type parameter `T`, found `HttpAuthentication<BearerAuth, ...>`
Next - tried a series of 'where' clauses on the T to specify it, inspired by the declarations on the service 'wrap' function - a very complicated bit of type declarations. For example:
// ONE TIME ADD 'uses' but keep around for other samples that need it...
use actix_service::boxed::{BoxService};
use actix_web::body::MessageBody;
use actix_web::dev::{Payload, Service, ServiceResponse, Transform};
use actix_web::http::header::Header;
use actix_web::{FromRequest, HttpRequest};
pub fn create_auth_middleware_3<T>() -> T
where T: Transform<S, ServiceRequest>,
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> {
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
maybe a hint from the compiler this time!
auth
| ^^^^ expected type parameter `T`, found `HttpAuthentication<BearerAuth, ...>`
|
= note: expected type parameter `T`
found struct `HttpAuthentication<BearerAuth, fn(..., ...) -> ... {validate_bearer}>`
the full type name has been written to ....
So I tried the actual type the compiler wrote to that file
pub fn create_auth_middleware_4() -> HttpAuthentication<BearerAuth, fn(ServiceRequest, BearerAuth) -> impl futures::Future<Output = std::result::Result<ServiceRequest, actix_web::Error>> {validate_bearer}> {
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
results in:
error[E0747]: constant provided when a type was expected
--> src/server/auth/middleware.rs:37:69
|
37 | ...h, fn(ServiceRequest, BearerAuth) -> impl futures::Future<Output = std::result::Result<ServiceRequest, actix_web::Error>> {validate_bearer}...
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
OK - maybe Rust doesn't like the explicit {validate_bearer}
stuff it wrote as part of the type.
pub fn create_auth_middleware_5() -> HttpAuthentication<BearerAuth, fn(ServiceRequest, BearerAuth) -> impl futures::Future<Output = std::result::Result<ServiceRequest, actix_web::Error>> > {
let auth = HttpAuthentication::bearer(validate_bearer);
auth
}
results in:
error[E0562]: `impl Trait` only allowed in function and inherent method return types, not in `fn` pointer return types
This thing I'm trying to do - wrap an expression into a separate function which goes in another file, is completely trivial in just about every programming language I've ever used (Python, prolog, C, C++, Java, Typescript, JavaScript, Ruby, Lisp). I'm sure there is a very simple way to do this in Rust as well, but I've failed to find it. Please help me to see what I'm missing, and how to do this sort of basic factoring of Rust code.
答案1
得分: 6
The ideal solution would be to do impl Fn(...) -> impl Future<...>
, but using impl
twice like this is currently not allowed. There is a workaround that is mentioned in another thread that works for this. This will hopefully be solved by either fully allowing it or by type_alias_impl_trait
in the future. For now, you can work around it by creating a new trait.
pub trait BearerFuture: Fn(ServiceRequest, BearerAuth) -> Self::Fut {
type Fut: Future<Output = Result<ServiceRequest, (Error, ServiceRequest)>>;
}
impl<F, Fut> BearerFuture for F
where
F: Fn(ServiceRequest, BearerAuth) -> Fut,
Fut: Future<Output = Result<ServiceRequest, (Error, ServiceRequest)>>,
{
type Fut = Fut;
}
pub fn create_auth_middleware_5() -> HttpAuthentication<BearerAuth, impl BearerFuture> {
HttpAuthentication::bearer(validate_bearer)
}
There's some nightly features which make these easier. Since these haven't entered stabilization, they are at a minimum 12 weeks from being in stable (1.70 releases today), and will likely take 6 months or more. I would expect type_alias_impl_trait
to be released first, with high likelihood of being released eventually, since it is incredibly useful for async
code and seems to cover the uses of impl_trait_in_fn_trait_return
. I mention these mostly for future readers or those already using nightly. Others should use the new trait method.
With type_alias_impl_trait
, you could do this.
#![feature(type_alias_impl_trait)]
type BearerFn = impl Fn(ServiceRequest, BearerAuth) -> BearerFuture;
type BearerFuture = impl Future<Output = Result<ServiceRequest, (Error, ServiceRequest)>>;
pub fn create_auth_middleware_5() -> HttpAuthentication<BearerAuth, BearerFn> {
HttpAuthentication::bearer(validate_bearer)
}
And with impl_trait_in_fn_trait_return
you could create this ugly mess.
#![feature(impl_trait_in_fn_trait_return)]
pub fn create_auth_middleware_5() -> HttpAuthentication<
BearerAuth,
impl Fn(
ServiceRequest,
BearerAuth,
) -> impl Future<Output = Result<ServiceRequest, (Error, ServiceRequest)>>,
> {
HttpAuthentication::bearer(validate_bearer)
}
Note: You will almost never use the fn
type in rust. You'll usually use one of the Fn
/FnMut
/FnOnce
traits.
英文:
The ideal solution would be to do impl Fn(...) -> impl Future<...>
, but using impl
twice like this is currently not allowed. There is a workaround that is mentioned in another thread that works for this. This will hopefully be solved by either fully allowing it or by type_alias_impl_trait
in the future. For now, you can work around it by creating a new trait. (playground)
pub trait BearerFuture: Fn(ServiceRequest, BearerAuth) -> Self::Fut {
type Fut: Future<Output = Result<ServiceRequest, (Error, ServiceRequest)>>;
}
impl<F, Fut> BearerFuture for F
where
F: Fn(ServiceRequest, BearerAuth) -> Fut,
Fut: Future<Output = Result<ServiceRequest, (Error, ServiceRequest)>>,
{
type Fut = Fut;
}
pub fn create_auth_middleware_5() -> HttpAuthentication<BearerAuth, impl BearerFuture> {
HttpAuthentication::bearer(validate_bearer)
}
There's some nightly features which make these easier. Since these haven't entered stabilization, they are at a minimum 12 weeks from being in stable (1.70 releases today), and will likely take 6 months or more. I would expect type_alias_impl_trait
to be released first, with high likelihood of being released eventually, since it is incredibly useful for async
code and seems to cover the uses of impl_trait_in_fn_trait_return
. I mention these mostly for future readers or those already using nightly. Others should use the new trait method.
With type_alias_impl_trait
, you could do this. (playground)
#![feature(type_alias_impl_trait)]
type BearerFn = impl Fn(ServiceRequest, BearerAuth) -> BearerFuture;
type BearerFuture = impl Future<Output = Result<ServiceRequest, (Error, ServiceRequest)>>;
pub fn create_auth_middleware_5() -> HttpAuthentication<BearerAuth, BearerFn> {
HttpAuthentication::bearer(validate_bearer)
}
And with impl_trait_in_fn_trait_return
you could create this ugly mess. (playground)
#![feature(impl_trait_in_fn_trait_return)]
pub fn create_auth_middleware_5() -> HttpAuthentication<
BearerAuth,
impl Fn(
ServiceRequest,
BearerAuth,
) -> impl Future<Output = Result<ServiceRequest, (Error, ServiceRequest)>>,
> {
HttpAuthentication::bearer(validate_bearer)
}
Note: You will almost never use the fn
type in rust. You'll usually use one of the Fn
/FnMut
/FnOnce
traits.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论