You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rust中反序列化异步函数名并调用带反序列化参数的函数?

解决Rust中通过TOML配置异步函数序列的通用方案

为什么初始方案不可行

你最初尝试用dyn Fn() -> dyn Future<Output = Result<(), ()>>作为Config的字段类型,这个方案走不通的核心原因有两个:

  1. serde无法序列化/反序列化 trait 对象:serde 需要明确的类型信息完成序列化,而动态 trait 对象(dyn Trait)没有固定内存布局,无法被自动序列化。
  2. 异步函数的类型擦除问题:每个异步函数编译后会生成独特的匿名Future类型,即使返回值一致,类型也不相同,无法直接统一到dyn Future中(手动类型擦除也解决不了序列化问题)。

方案一:用过程宏自动生成Enum和执行逻辑

你的PoC用Enum实现了核心逻辑,但需要用户手动维护Enum变体和perform方法,这个痛点可以通过过程宏自动解决。下面是具体实现步骤:

1. 定义过程宏依赖

在Cargo.toml中添加宏相关依赖:

[lib]
proc-macro = true

[dependencies]
syn = { version = "2.0", features = ["full"] }
quote = "1.0"
serde = { version = "1.0", features = ["derive"] }
toml = "0.8"
indexmap = { version = "2.0", features = ["serde"] }
tokio = { version = "1.0", features = ["full"] }

2. 编写过程宏

创建src/lib.rs,实现属性宏标记要注册的异步函数,自动生成对应的Enum和perform方法:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn register(_args: TokenStream, input: TokenStream) -> TokenStream {
    let func = parse_macro_input!(input as ItemFn);
    let func_name = &func.sig.ident;
    let func_ident_str = func_name.to_string();
    
    // 生成首字母大写的Enum变体名称
    let variant_name = syn::Ident::new(
        &func_ident_str[0..1].to_uppercase() + &func_ident_str[1..],
        func_name.span()
    );
    
    // 提取函数参数类型
    let args: Vec<_> = func.sig.inputs.iter().map(|arg| {
        match arg {
            syn::FnArg::Typed(pat_type) => &pat_type.ty,
            _ => panic!("仅支持带命名参数的函数"),
        }
    }).collect();

    // 生成调用时的参数绑定
    let call_args: Vec<_> = func.sig.inputs.iter().enumerate().map(|(i, arg)| {
        match arg {
            syn::FnArg::Typed(pat_type) => &pat_type.pat,
            _ => panic!("仅支持带命名参数的函数"),
        }
    }).collect();

    // 生成Enum变体定义
    let variant_def = if args.is_empty() {
        quote! { #variant_name, }
    } else {
        quote! { #variant_name(#(#args),*), }
    };

    // 生成match分支逻辑
    let match_arm = if args.is_empty() {
        quote! {
            #variant_name => #func_name().await,
        }
    } else {
        quote! {
            #variant_name(#(#call_args),*) => #func_name(#(#call_args),*).await,
        }
    };

    // 输出完整代码:原函数+自动生成的Enum和perform方法
    let expanded = quote! {
        #func

        #[derive(serde::Deserialize, serde::Serialize)]
        #[serde(tag = "function", content = "args")]
        pub enum RegisteredFunctions {
            #variant_def
        }

        impl RegisteredFunctions {
            pub async fn perform(&self) -> Result<(), ()> {
                match self {
                    #match_arm
                }
            }
        }
    };

    expanded.into()
}

3. 用户侧使用示例

用户只需用#[register]标记异步函数,无需手动维护Enum:

use your_lib::register;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use toml::toml;

#[register]
async fn without_args() -> Result<(), ()> {
    println!("无参数函数执行");
    Ok(())
}

#[register]
async fn with_args(arg: String) -> Result<(), ()> {
    println!("带参数函数执行:arg = {arg}!");
    Ok(())
}

#[register]
async fn sum_args(x: u64, y: u64) -> Result<(), ()> {
    println!("求和函数执行:{x} + {y} = {}!", x + y);
    Ok(())
}

#[derive(Serialize, Deserialize)]
struct Config {
    functions: IndexMap<String, RegisteredFunctions>,
}

#[tokio::main]
async fn main() {
    let toml = toml! {
        [functions.foo]
        function = "WithoutArgs"

        [functions.bar]
        function = "WithArgs"
        args = "baz"

        [functions.sum]
        function = "SumArgs"
        args = [ 1, 2 ]
    };
    let config: Config = toml.try_into().unwrap();

    for func in config.functions {
        func.1.perform().await.unwrap();
    }
}

这个方案让用户专注于编写业务函数,宏自动处理类型映射和执行逻辑,完全避免手动维护的繁琐。

方案二:动态函数注册与调用(无宏方案)

如果不想依赖过程宏,可以采用trait 对象 + 注册中心的方案,让用户动态注册函数,运行时根据TOML配置查找执行:

1. 定义核心Trait和注册中心

use std::collections::HashMap;
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use serde::Value;

#[async_trait]
pub trait AsyncCallable {
    async fn call(&self, args: &Value) -> Result<(), ()>;
}

pub struct FunctionRegistry {
    functions: HashMap<String, Box<dyn AsyncCallable + Send + Sync>>,
}

impl FunctionRegistry {
    pub fn new() -> Self {
        Self {
            functions: HashMap::new(),
        }
    }

    // 注册无参数函数
    pub fn register<F, Fut>(&mut self, name: &str, func: F)
    where
        F: Fn() -> Fut + Send + Sync + 'static,
        Fut: std::future::Future<Output = Result<(), ()>> + Send + 'static,
    {
        struct Wrapper<F, Fut>(F);

        #[async_trait]
        impl<F, Fut> AsyncCallable for Wrapper<F, Fut>
        where
            F: Fn() -> Fut + Send + Sync + 'static,
            Fut: std::future::Future<Output = Result<(), ()>> + Send + 'static,
        {
            async fn call(&self, _args: &Value) -> Result<(), ()> {
                (self.0)().await
            }
        }

        self.functions.insert(name.to_string(), Box::new(Wrapper(func)));
    }

    // 注册单参数函数
    pub fn register_with_arg<F, Fut, A>(&mut self, name: &str, func: F)
    where
        F: Fn(A) -> Fut + Send + Sync + 'static,
        Fut: std::future::Future<Output = Result<(), ()>> + Send + 'static,
        A: DeserializeOwned + Send + Sync + 'static,
    {
        struct Wrapper<F, Fut, A>(F);

        #[async_trait]
        impl<F, Fut, A> AsyncCallable for Wrapper<F, Fut, A>
        where
            F: Fn(A) -> Fut + Send + Sync + 'static,
            Fut: std::future::Future<Output = Result<(), ()>> + Send + 'static,
            A: DeserializeOwned + Send + Sync + 'static,
        {
            async fn call(&self, args: &Value) -> Result<(), ()> {
                let arg = serde_json::from_value(args.clone()).map_err(|_| ())?;
                (self.0)(arg).await
            }
        }

        self.functions.insert(name.to_string(), Box::new(Wrapper(func)));
    }

    // 注册双参数函数
    pub fn register_with_two_args<F, Fut, A, B>(&mut self, name: &str, func: F)
    where
        F: Fn(A, B) -> Fut + Send + Sync + 'static,
        Fut: std::future::Future<Output = Result<(), ()>> + Send + 'static,
        A: DeserializeOwned + Send + Sync + 'static,
        B: DeserializeOwned + Send + Sync + 'static,
    {
        struct Wrapper<F, Fut, A, B>(F);

        #[async_trait]
        impl<F, Fut, A, B> AsyncCallable for Wrapper<F, Fut, A, B>
        where
            F: Fn(A, B) -> Fut + Send + Sync + 'static,
            Fut: std::future::Future<Output = Result<(), ()>> + Send + 'static,
            A: DeserializeOwned + Send + Sync + 'static,
            B: DeserializeOwned + Send + Sync + 'static,
        {
            async fn call(&self, args: &Value) -> Result<(), ()> {
                let (a, b) = serde_json::from_value(args.clone()).map_err(|_| ())?;
                (self.0)(a, b).await
            }
        }

        self.functions.insert(name.to_string(), Box::new(Wrapper(func)));
    }

    // 根据函数名和参数执行
    pub async fn run(&self, name: &str, args: &Value) -> Result<(), ()> {
        let func = self.functions.get(name).ok_or(())?;
        func.call(args).await
    }
}

2. 用户侧使用示例

use your_lib::FunctionRegistry;
use serde::Value;
use toml::toml;

async fn without_args() -> Result<(), ()> {
    println!("无参数函数执行");
    Ok(())
}

async fn with_args(arg: String) -> Result<(), ()> {
    println!("带参数函数执行:arg = {arg}!");
    Ok(())
}

async fn sum_args(x: u64, y: u64) -> Result<(), ()> {
    println!("求和函数执行:{x} + {y} = {}!", x + y);
    Ok(())
}

#[derive(serde::Deserialize)]
struct FunctionConfig {
    function: String,
    args: Option<Value>,
}

#[derive(serde::Deserialize)]
struct Config {
    functions: indexmap::IndexMap<String, FunctionConfig>,
}

#[tokio::main]
async fn main() {
    let mut registry = FunctionRegistry::new();
    registry.register("without_args", without_args);
    registry.register_with_arg("with_args", with_args);
    registry.register_with_two_args("sum_args", sum_args);

    let toml = toml! {
        [functions.foo]
        function = "without_args"

        [functions.bar]
        function = "with_args"
        args = "baz"

        [functions.sum]
        function = "sum_args"
        args = [ 1, 2 ]
    };
    let config: Config = toml.try_into().unwrap();

    for (_key, func_config) in config.functions {
        let args = func_config.args.as_ref().unwrap_or(&Value::Null);
        registry.run(&func_config.function, args).await.unwrap();
    }
}

这个方案无需宏依赖,通过trait对象实现动态注册,灵活性更高,可扩展支持更多参数数量的函数。

方案对比

方案类型优点缺点
过程宏自动生成Enum用户使用简洁,无需手动处理类型映射需要掌握过程宏开发,依赖syn/quote库
动态注册trait对象无宏依赖,灵活性高,支持动态添加函数需要为不同参数数量编写注册方法,参数解析依赖serde_json

你可以根据库的定位选择合适的方案:追求用户使用简洁性选过程宏方案;追求灵活性和无宏依赖选动态注册方案。

内容的提问来源于stack exchange,提问作者Callum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 05:24:56