Rust 是一种严格的静态类型语言。具体来说,它没有函数重载或可选的函数参数。

直到我在 Axum 中惊讶地看到这样的东西:

let app = Router::new()
    .route("/users", get(get_users))
    .route("/products", get(get_product));

async fn get_users(Query(params): Query<Params>) -> impl IntoResponse {
    let users = /* ... */

    Json(users)
}

async fn get_product(State(db): State<Db>, Json(payload): Json<Payload>) -> String {
let product = /* ... */

product.to_string()
}

get方法可以接收指向各种类型的函数指针!这是什么黑魔法? 🤯

我想我必须创建一个简化版本来以便了解这个实现方式。

fn print_id(id: Id) {
    println!("id is {}", id.0);
}

// Param(param) is just pattern matching
fn print_all(Param(param): Param, Id(id): Id) {
    println!("param is {param}, id is {id}");
}

pub fn main() {
    let context = Context::new("magic".into(), 33);

    trigger(context.clone(), print_id);
    trigger(context.clone(), print_all);
}

在示例中,我们有一个 trigger 函数接收 Context 对象和函数指针。函数指针可能会接收 Id or Param 类型的 1 或 2 个参数。魔法?

组件

让我们看一下实现此目的的组件

Context

struct Context {
    param: String,
    id: u32,
}

在Axums 的情况下,Context 是接收状态 Request。这是我们的函数想要接收的“部分”的来源。在这个简化的示例中,它包含两个数据字段

FromContext Trait

trait FromContext {
    fn from_context(context: &Context) -> Self;
}

第一个技巧是 FromContext Trait。它将允许我们创建从 Context 对象中提取必要数据的“提取器”。例如

pub struct Param(pub String);

impl FromContext for Param {
    fn from_context(context: &Context) -> Self {
        Param(context.param.clone())
    }
}

这个特征将允许我们持有一个 Context, 但调用一个期望 Param 的函数. 稍后会详细介绍

Handler Trait

trait Handler<T> {
    fn call(self, context: Context);
}

第二个技巧是 Handler trait。我们将实现闭包类型的 Fn(T) 特征。是的,我们可以实现闭包类型的特征。这个实现将允许我们在函数调用和它的参数之间有一个“中间件”。 在这里,我们将调用该方法 FromContext::from_context ,将上下文转换为预期的函数参数,即 ParamId.

impl<F, T> Handler<T> for F
where
    F: Fn(T),
    T: FromContext,
{
    fn call(self, context: Context) {
        (self)(T::from_context(&context));
    }
}

为了支持多个函数参数,我们将继续为具有 2、3、4 等数量参数的闭包类型实现 Handler 。这里有趣的一点是,此实现与参数的顺序无关 - 它将支持 fn foo(p: Param, id: Id)fn foo(id: Id, p: Param)

impl<T1, T2, F> Handler<(T1, T2)> for F
where
    F: Fn(T1, T2),
    T1: FromContext,
    T2: FromContext,
{
    fn call(self, context: Context) {
        (self)(T1::from_context(&context), T2::from_context(&context));
    }
}

将一切整合在一起

trigger 函数的实现现在很简单

pub fn trigger<T, H>(context: Context, handler: H)
where
    H: Handler<T>,
{
    handler.call(context);
}

让我们检查一下此调用会发生什么情况

let context = Context::new("magic".into(), 33);

trigger(context.clone(), print_id);
  • print_id Fn(Id) 的类型具有 的 Handler<Id> 实现。
  • 调用 Handler::call 该方法,我们 Id::from_context(context) 从中返回结构体的 Id 实例。
  • print_id 使用它期望的参数调用。

魔法揭开了神秘面纱。