#async-trait #traits #async #variadic #future #middleware #request-response

async-variadic

Rust中的简单异步可变函数,具有特质量界

3个稳定版本

1.1.1 2022年7月18日
1.0.0 2022年7月17日

#674异步

MIT 许可证

19KB
102

async-variadic

Latest version Documentation License

提供了一种通过支持n元参数的特质量界传递异步函数的方式。这段代码受到了特质量界使用的启发,以便支持可变参数。

示例

use async_variadic::AsyncFn;

async fn min() {}
async fn test2(_s: String) -> i32 { 3 }
async fn test3(a: i32, b: i32) -> i32 { a + b }

fn assert_impl_fn<T, O>(_: impl AsyncFn<T, O>) {}

assert_impl_fn(min);
assert_impl_fn(test2);
assert_impl_fn(test3);

由于输出受特质量界限制,我们甚至可以像这样为输入和输出添加特质量界

struct Request {}
struct Response {}
struct Body {}

impl From<Request> for (Request,) {
    fn from(req: Request) -> Self {
        (req,)
    }
}

impl From<Request> for (Body,) {
    fn from(_req: Request) -> Self {
        (Body {},)
    }
}

impl From<&'static str> for Response {
    fn from(_s: &'static str) -> Self {
        Response {}
    }
}

fn assert_impl_output<T: From<Request>, O: Into<Response>>(_: impl AsyncFn<T, O>) {}

#[test]
fn test_trait_bounds() {
    async fn with_request_resp(_req: Request) -> &'static str {
        "hello"
    }

    async fn with_body_resp(_body: Body) -> &'static str {
        "hello"
    }

    assert_impl_output(with_request_resp);
    assert_impl_output(with_body_resp);
}

actix-web处理程序 + 使用Into/From Traits

actix-web 实现了此功能,用于处理函数,将 FromRequest 转换为任何实现 Responder 的类型。这样就可以编写一个可以绑定任意数量和位置的任意类型数据(只要它们实现了相应的特质量界)的web应用。

背景:冲突实现

在此背后,你可能尝试为具有给定数量的参数的任何 函数 实现特质量界。查看特质量界文章中提供的示例

trait VariadicFunction {
  fn call(&self, req: &[f64]) -> f64;
}

impl<Func> VariadicFunction for Func
where Func : Fn(f64)->f64 {
  fn eval(&self,args : &[f64])->f64 {
    (self)(args[0])
  }
}

impl<Func> VariadicFunction for Func
where Func : Fn(f64,f64)->f64 {
  fn eval(&self,args : &[f64])->f64 {
    (self)(args[0],args[1])
  } }

fn evaluate<F:VariadicFunction>(func : F, args: &[f64]) -> f64{
  func.eval(args)
}

编译器将输出类似以下内容

error[E0119]: conflicting implementations of trait `VariadicFunction`:
  --> src/lib.rs:12:1
   |
5  | / impl<Func> VariadicFunction for Func
6  | | where Func : Fn(f64)->f64 {
7  | |   fn eval(&self,args : &[f64])->f64 {
8  | |     (self)(args[0])
9  | |   }
10 | | }
   | |_- first implementation here
11 |
12 | / impl<Func> VariadicFunction for Func
13 | | where Func : Fn(f64,f64)->f64 {
14 | |   fn eval(&self,args : &[f64])->f64 {
15 | |     (self)(args[0],args[1])
16 | |   }
17 | | }
   | |_^ conflicting implementation

error: aborting due to previous error

当我尝试创建一个可以处理可变数量参数的 Middleware 特质量界时,我遇到了类似的问题。我之所以这样做,是因为我认为可能很有趣将只指定一个或多个参数的闭包传递进去。

#[async_trait]
pub trait Middleware<'a, 'b>: Send + Sync {
    #[must_use = "handle future must be used"]
    async fn handle(&self, request: &'a mut Request, response: &'b mut Response);
}

#[async_trait]
impl<'a, 'b, F, Fut, Res> Middleware<'a, 'b> for F
where
    F: Send + Sync + 'a + Fn(&'a mut Request, &'b mut Response) -> Fut,
    Fut: Future<Output = Res> + Send + 'b,
{
    async fn handle(&self, request: &'a mut Request, response: &'b mut Response) {
        (self)(request, response).await;
    }
}

这里我将 F 类型限定为接受两个参数的 Fn 特质量界。如果我想指定一个只传递请求的闭包,它将看起来像这样

#[async_trait]
pub trait Middleware<'a, 'b>: Send + Sync {
    #[must_use = "handle future must be used"]
    async fn handle(&self, request: &'a mut Request, response: &'b mut Response);
}

#[async_trait]
impl<'a, 'b, F, Fut, Res> Middleware<'a, 'b> for F
where
    F: Send + Sync + 'a + Fn(&'a mut Request, &'b mut Response) -> Fut,
    Fut: Future<Output = Res> + Send + 'b,
{
    async fn handle(&self, request: &'a mut Request, response: &'b mut Response) {
        (self)(request, response).await;
    }
}

#[async_trait]
impl<'a, 'b, F, Fut, Res> Middleware<'a, 'b> for F
where
    F: Send + Sync + 'a + Fn(&'a mut Request) -> Fut,
    Fut: Future<Output = Res> + Send + 'b,
{
    async fn handle(&self, request: &'a mut Request, response: &'b mut Response) {
        (self)(request).await;
    }
}

不幸的是,我遇到了与上面相同的编译错误。主要原因是(如文章和问题 #60074 中讨论的那样),闭包可以同时实现 Fn 特质量界并产生未定义的行为。

impl FnOnce<(u32,)> for Foo {
    type Output = u32;
    extern "rust-call" fn call_once(self, args: (u32,)) -> Self::Output {
        args.0
    }
}

impl FnOnce<(u32, u32)> for Foo {
    type Output = u32;
    extern "rust-call" fn call_once(self, args: (u32, u32)) -> Self::Output {
        args.0
    }
}

这不是我们想要的解决方案。我们需要找到另一种方法!

特性专用化

如文章中所述,解决这个问题的方法是创建一个带有 Args 的特性专用化类型。

trait VariadicFunction<ArgList> {
  fn eval(&self, args: &[f64]) -> f64;
}

然后我们可以为每个位置参数实现一个 Fn 特性约束,同时将此类型提供给 VariadicFunction。

impl<Func> VariadicFunction<f64> for Func
where Func : Fn(f64)->f64 {
  fn eval(&self,args : &[f64])->f64 {
    (self)(args[0])
  }
}

impl<Func> VariadicFunction<(f64,f64)> for Func
where Func : Fn(f64,f64)->f64 {
  fn eval(&self,args : &[f64])->f64 {
    (self)(args[0],args[1])
  }
}

fn evaluate<ArgList, F>(func : F, args: &[f64]) -> f64
where F: VariadicFunction<ArgList>{
  func.eval(args)
}

请注意,当编译器将 VariadicFunction 特性单态化后,它实际上是两个不同的特性。我们正在为具有不同参数的 函数 实现完全不同的特性。让我们为异步操作扩展这一点!

抽象为 AsyncFn

异步函数不过是一个返回 Future 的函数。

pub trait Future {
    type Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

相关输出是 Poll<Self::Output> 返回 Poll::Ready(output) 的结果。我们可以定义一个与 Future 特性类似的 AsyncFn 特性,其主要目的是作为函数的特性约束。

pub trait AsyncFn<Args> {
    type Output;
    type Future: Future<Output = Self::Output>;

    fn call(&self, args: Args) -> Self::Future;
}

现在剩下的只是为不同的闭包实现这个特性。最简单的情况是一个空的异步函数。

impl<Func, Fut> AsyncFn<()> for Func
where
    Func: Fn() -> Fut,
    Fut: Future
{
    type Output = Fut::Output;
    type Future = Fut;

    fn call(&self, args: ()) -> Self::Future {
        (self)()
    }
}

第二种情况是一个接受一个参数的函数。 注意 我们不需要指定输出,因为 Future::Output 已经为我们指定了。

impl<Func, Fut, A> AsyncFn<(A,)> for Func
where
    Func: Fn(A) -> Fut,
    Fut: Future
{
    type Output = Fut::Output;
    type Future = Fut;

    fn call(&self, args: (A,)) -> Self::Future {
        let (a,) = args;
        (self)(a)
    }
}

我们可以修改这里的 call 函数,将参数元组解构到局部变量中。

impl<Func, Fut, A> AsyncFn<(A,)> for Func
where
    Func: Fn(A) -> Fut,
    Fut: Future
{
    type Output = Fut::Output;
    type Future = Fut;

    fn call(&self, (A,): (A,)) -> Self::Future {
        (self)(A)
    }
}

这可以扩展以支持多个参数,类似于上面的实现。

impl<Func, Fut, A, B> AsyncFn<(A,B)> for Func
where
    Func: Fn(A) -> Fut,
    Fut: Future
{
    type Output = Fut::Output;
    type Future = Fut;

    fn call(&self, (A,B): (A,B)) -> Self::Future {
        (self)(A, B)
    }
}

使用宏自动实现

虽然手动实现这些变化很简单,但我们可以编写一个简单的宏来自动构建这些。

macro_rules! ary ({ $($param:ident)* } => {
    impl<Func, Fut, $($param,)*> AsyncFn<($($param,)*)> for Func
    where
        Func: Fn($($param),*) -> Fut,
        Fut: Future
    {
        type Output = Fut::Output;
        type Future = Fut;

        #[inline]
        #[allow(non_snake_case)]
        fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future {
            (self)($($param,)*)
        }
    }
});

这个宏简单地将 $(param:ident)* 模式指定为 impl<> 的泛型参数、AsyncFn<T> 中的特性专用化、Func 参数上的特性约束,以及最终的 call 函数。现在我们只需要用不同的标识符调用这个宏就可以了。

ary! {}
ary! { A }
ary! { A B }
ary! { A B C }
ary! { A B C D }
ary! { A B C D E }
ary! { A B C D E F }
ary! { A B C D E F G }
ary! { A B C D E F G H }
ary! { A B C D E F G H I }
ary! { A B C D E F G H I J }
ary! { A B C D E F G H I J K }
ary! { A B C D E F G H I J K L }

这将允许我们指定最多 12 个不同的参数。每个新的泛型标识符都将包含在一个全新的特性实现中。

测试

下面的测试展示了我们如何现在指定任何类型的异步函数,它捕获任何类型的变量(以任何顺序)并返回任何类型(如 Future::Output 所示)。

#[cfg(test)]
mod tests {
    use super::*;

    fn assert_impl_fn<T>(_: impl AsyncFn<T>) {}

    #[test]
    fn test_args() {
        async fn min() {}
        async fn min_output() -> i32 {
            4
        }
        async fn with_req(_req: String) -> &'static str {
            "foo"
        }
        #[rustfmt::skip]
        #[allow(clippy::too_many_arguments, clippy::just_underscores_and_digits)]
        async fn max(
            _01: (), _02: (), _03: (), _04: (), _05: (), _06: (),
            _07: (), _08: (), _09: (), _10: (), _11: (), _12: (),
        ) {}

        assert_impl_fn(min);
        assert_impl_fn(with_req);
        assert_impl_fn(min_output);
        assert_impl_fn(max);
    }
}

这里的用例现在是我们可以有一个像 assert_impl_fn<T> 这样的函数,它实际上接受一个异步函数或闭包作为参数!这对于我们的处理程序、中间件和 Web 服务器实现非常有用。

特性约束

为了允许对输出使用特性约束,特性需要修改为使用 async-trait。这对于改变输入和输出工作得很好。

#[async_trait]
pub trait AsyncFn<Args, Output> {
    async fn call(&self, args: Args) -> Output;
}

现在,宏已更新为需要 SendSync 以支持 async-trait

macro_rules! ary ({ $($param:ident)* } => {
    #[async_trait::async_trait]
    impl<Func, Fut, $($param:Send + 'static,)*> AsyncFn<($($param,)*), Fut::Output> for Func
    where
        Func: Send + Sync + Fn($($param),*) -> Fut,
        Fut: Future + Send
    {
        #[inline]
        #[allow(non_snake_case)]
        async fn call(&self, ($($param,)*): ($($param,)*)) -> Fut::Output {
            (self)($($param,)*).await
        }
    }
});

随着这个更改,虽然它确实限制了未来输出的范围,但我相信这对人机工程学来说是一个净收益,因为现在我们可以这样做

struct Request {}
struct Response {}
struct Body {}

impl From<Request> for (Request,) {
    fn from(req: Request) -> Self {
        (req,)
    }
}

impl From<Request> for (Body,) {
    fn from(_req: Request) -> Self {
        (Body {},)
    }
}

impl From<&'static str> for Response {
    fn from(_s: &'static str) -> Self {
        Response {}
    }
}

fn assert_impl_output<T: From<Request>, O: Into<Response>>(_: impl AsyncFn<T, O>) {}

#[test]
fn test_trait_bounds() {
    async fn with_request_resp(_req: Request) -> &'static str {
        "hello"
    }

    async fn with_body_resp(_body: Body) -> &'static str {
        "hello"
    }

    assert_impl_output(with_request_resp);
    assert_impl_output(with_body_resp);
}

依赖项

~300–760KB
~18K SLoC