decltype, dyn, impl traits, and how to declare the return type of a function when refactoring

206 Views Asked by At

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

There are 1 best solutions below

14
drewtato On BEST ANSWER

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.