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

如何为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 18:13:16