如何在Rust中反序列化异步函数名并调用带反序列化参数的函数?
解决Rust中通过TOML配置异步函数序列的通用方案
为什么初始方案不可行
你最初尝试用dyn Fn() -> dyn Future<Output = Result<(), ()>>作为Config的字段类型,这个方案走不通的核心原因有两个:
- serde无法序列化/反序列化 trait 对象:serde 需要明确的类型信息完成序列化,而动态 trait 对象(
dyn Trait)没有固定内存布局,无法被自动序列化。 - 异步函数的类型擦除问题:每个异步函数编译后会生成独特的匿名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
相关产品推荐
相关产品推荐

