如何为Rust函数定义关联唯一ID以实现serde序列化结构体?
解决方案
方案一:自定义属性宏自动收集ID与函数映射
这是最便捷的实现方式,通过自定义属性宏给每个实现函数标注唯一ID,编译期自动生成全局映射表,无需手动维护对应关系。
步骤1:配置依赖与实现属性宏
首先在Cargo.toml中添加所需依赖:
[dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" lazy_static = "1.4" ctor = "0.2" # 如果单独创建proc-macro crate,需在对应Cargo.toml中添加: # [lib] # proc-macro = true # [dependencies] # proc-macro2 = "1.0" # quote = "1.0" # syn = { version = "2.0", features = ["full", "parsing"] }
创建proc-macro crate(如effect_macros),实现属性宏:
// effect_macros/src/lib.rs use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, AttributeArgs, ItemFn}; #[proc_macro_attribute] pub fn effect_id(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as AttributeArgs); let id = match args.first() { Some(syn::NestedMeta::Lit(syn::Lit::Str(s))) => s.value(), _ => panic!("参数必须是字符串类型的唯一ID"), }; let func = parse_macro_input!(input as ItemFn); let func_ident = &func.sig.ident; let expanded = quote! { #func #[ctor::ctor] fn register_#func_ident() { crate::effect_registry::EFFECT_FUNC_MAP.lock().unwrap().insert(#id, #func_ident as fn(&mut GameState, Source) -> Result<()>); crate::effect_registry::EFFECT_ID_MAP.lock().unwrap().insert(#func_ident as fn(&mut GameState, Source) -> Result<()>, #id); } }; TokenStream::from(expanded) }
在主crate中创建全局注册表:
// src/effect_registry.rs use lazy_static::lazy_static; use std::collections::HashMap; use std::sync::Mutex; lazy_static! { pub static ref EFFECT_FUNC_MAP: Mutex<HashMap<String, fn(&mut GameState, Source) -> Result<()>>> = Mutex::new(HashMap::new()); pub static ref EFFECT_ID_MAP: Mutex<HashMap<fn(&mut GameState, Source) -> Result<()>, String>> = Mutex::new(HashMap::new()); }
步骤2:为函数标注ID
直接给每个实现函数添加属性宏标注:
use effect_macros::effect_id; #[effect_id("heal_player")] fn heal_player(state: &mut GameState, source: Source) -> Result<()> { // 具体实现逻辑 Ok(()) } #[effect_id("apply_poison")] fn apply_poison(state: &mut GameState, source: Source) -> Result<()> { // 具体实现逻辑 Ok(()) }
步骤3:为Effect实现Serde序列化
为Effect结构体自定义序列化/反序列化逻辑,将函数指针转换为ID存储:
use serde::{Serialize, Deserialize, Serializer, Deserializer}; use crate::effect_registry::{EFFECT_FUNC_MAP, EFFECT_ID_MAP}; #[derive(Debug)] pub struct Effect { pub source: Source, pub duration: Duration, pub implementation: fn(&mut GameState, Source) -> Result<()>, } impl Serialize for Effect { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let id = EFFECT_ID_MAP.lock().unwrap() .get(&self.implementation) .cloned() .ok_or_else(|| serde::ser::Error::custom("未注册的Effect函数"))?; serializer.serialize_struct("Effect", &[ ("source", &self.source), ("duration", &self.duration), ("implementation_id", &id), ]) } } impl<'de> Deserialize<'de> for Effect { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct SerdeEffect { source: Source, duration: Duration, implementation_id: String, } let ser_effect = SerdeEffect::deserialize(deserializer)?; let func = EFFECT_FUNC_MAP.lock().unwrap() .get(&ser_effect.implementation_id) .copied() .ok_or_else(|| serde::de::Error::custom(format!("未知的Effect ID: {}", ser_effect.implementation_id)))?; Ok(Effect { source: ser_effect.source, duration: ser_effect.duration, implementation: func, }) } }
方案二:枚举封装函数指针(无宏依赖)
如果Effect函数数量不多,可直接用枚举变体对应每个函数,变体本身作为可序列化的ID:
use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Clone, Copy)] enum EffectId { HealPlayer, ApplyPoison, } impl EffectId { fn to_function(self) -> fn(&mut GameState, Source) -> Result<()> { match self { EffectId::HealPlayer => heal_player, EffectId::ApplyPoison => apply_poison, } } fn from_function(func: fn(&mut GameState, Source) -> Result<()>) -> Option<Self> { match func { f if f == heal_player => Some(EffectId::HealPlayer), f if f == apply_poison => Some(EffectId::ApplyPoison), _ => None, } } } // 为Effect实现序列化/反序列化 impl Serialize for Effect { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let effect_id = EffectId::from_function(self.implementation) .ok_or_else(|| serde::ser::Error::custom("未注册的Effect函数"))?; serializer.serialize_struct("Effect", &[ ("source", &self.source), ("duration", &self.duration), ("implementation", &effect_id), ]) } } // 反序列化逻辑类似,从EffectId转换为函数指针
此方案无需宏,代码直观,但需手动维护枚举与函数的对应关系,适合函数数量较少的场景。
方案三:手动维护映射表(最简但易出错)
若不想引入宏或枚举,可手动创建双向映射表,序列化时手动转换:
use lazy_static::lazy_static; use std::collections::HashMap; lazy_static! { static ref ID_TO_FUNC: HashMap<String, fn(&mut GameState, Source) -> Result<()>> = { let mut map = HashMap::new(); map.insert("heal_player".into(), heal_player); map.insert("apply_poison".into(), apply_poison); map }; static ref FUNC_TO_ID: HashMap<fn(&mut GameState, Source) -> Result<()>, String> = { let mut map = HashMap::new(); map.insert(heal_player, "heal_player".into()); map.insert(apply_poison, "apply_poison".into()); map }; }
随后为Effect实现Serde逻辑时,直接使用上述映射表转换即可。此方法最简单,但每次新增函数都需手动更新映射表,易遗漏出错。
内容的提问来源于stack exchange,提问作者Derek Thurn
相关产品推荐
相关产品推荐

