3个稳定版本
1.1.1 | 2022年7月18日 |
---|---|
1.0.0 | 2022年7月17日 |
#674 在 异步
19KB
102 行
async-variadic
提供了一种通过支持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;
}
现在,宏已更新为需要 Send 和 Sync 以支持 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