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.
The ideal solution would be to do
impl Fn(...) -> impl Future<...>, but usingimpltwice 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 bytype_alias_impl_traitin the future. For now, you can work around it by creating a new trait. (playground)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_traitto be released first, with high likelihood of being released eventually, since it is incredibly useful forasynccode and seems to cover the uses ofimpl_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)And with
impl_trait_in_fn_trait_returnyou could create this ugly mess. (playground)Note: You will almost never use the
fntype in rust. You'll usually use one of theFn/FnMut/FnOncetraits.