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

自定义NetworkSerializable trait的Derive宏实现报错求解决

解决Rust自定义Derive宏实现NetworkSerializable的问题

问题描述

我在客户端/服务端系统中自定义了NetworkSerializable trait,希望让用户自定义的消息枚举通过#[derive(NetworkSerializable)]自动实现该trait,减少重复编写序列化逻辑的工作量。

trait定义如下:

pub trait NetworkSerializable {
    fn id(&self) -> usize;
    fn size(&self) -> usize;
    fn serialize(self) -> Vec<u8>;
    fn deserialize(id: usize, data: Vec<u8>) -> Self;
}

要求id()方法为枚举的每个变体返回唯一ID,用于标识消息类型。例如:

enum NetworkMessages {
    ErrorMessage,
    SpawnPlayer(usize, bool, Transform), // player id, is_mine, position
    MovePlayer(usize, Transform), // player id, new_position
    DestroyPlayer(usize) // player_id
}

但编写Derive宏时遇到首条错误:proc macro NetworkSerializable not expanded: no proc macro dylib present,宏实现代码如下:

#[proc_macro_derive(NetworkSerializable)]
pub fn network_serializable_derive(input: TokenStream) -> TokenStream {
    // Construct a representation of Rust code as a syntax tree
    // that we can manipulate
    let ast = syn::parse(input).unwrap();

    // Build the trait implementation
    impl_network_serializable_macro(&ast)
}


fn impl_network_serializable_macro(ast: &syn::DeriveInput) -> TokenStream {
    
    // get enum name
    let ref name = ast.ident;
    let ref data = ast.data;

    let (id_func, size_func, serialize_func, deserialize_func) = match data {
        // Only if data is an enum, we do parsing
        Data::Enum(data_enum) => {

            // Iterate over enum variants

            let mut id_func_internal = TokenStream2::new();
            let mut variant_id: usize = 0;

            for variant in &data_enum.variants {

                // add the branch for the variant
                id_func_internal.extend(quote_spanned!{
                    variant.span() => &variant_id,
                });
                variant_id += 1;
            }

            
            (id_func_internal, (), (), ())
        }
        _ => {(TokenStream2::new(), (), (), ())},
    };

    let expanded = quote! {
        impl NetworkSerializable for #name {
            // variant_checker_functions gets replaced by all the functions
            // that were constructed above
            fn size(&self) -> usize {
                match &self {
                    #id_func
                }
            }
            /*
            #size_func
            #serialize_func
            #deserialize_func
            */
        }
    };

    expanded.into()
}

解决方案

一、修复Proc Macro Dylib构建问题

Proc宏必须编译为动态库,这是解决"no proc macro dylib present"错误的核心。检查并修改Cargo.toml配置:

[package]
name = "network-serializable-derive"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true  # 必须设置此选项,告诉Cargo编译为proc宏动态库

[dependencies]
syn = { version = "2.0", features = ["full", "extra-traits"] }
quote = "1.0"
proc-macro2 = "1.0"
byteorder = "1.4"  # 用于示例中的序列化/反序列化逻辑

二、修正宏实现的语法与逻辑错误

原宏存在语法树引用错误、match分支生成不合法、方法实现不完整等问题,以下是修复后的完整实现:

use proc_macro::TokenStream;
use proc_macro2::TokenStream2;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Ident, Variant};

#[proc_macro_derive(NetworkSerializable)]
pub fn network_serializable_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(input as DeriveInput);
    impl_network_serializable_macro(&ast).into()
}

fn impl_network_serializable_macro(ast: &DeriveInput) -> TokenStream2 {
    let name = &ast.ident;

    let (id_impl, size_impl, serialize_impl, deserialize_impl) = match &ast.data {
        Data::Enum(data_enum) => {
            let variants = &data_enum.variants;
            let mut id_branches = Vec::new();
            let mut size_branches = Vec::new();
            let mut serialize_branches = Vec::new();
            let mut deserialize_arms = Vec::new();

            for (idx, variant) in variants.iter().enumerate() {
                let variant_ident = &variant.ident;
                let variant_id = idx as usize;

                // 生成id()方法的match分支
                id_branches.push(quote! {
                    #name::#variant_ident { .. } | #name::#variant_ident(_*) => #variant_id,
                });

                // 生成size()方法的match分支
                let size_expr = generate_size_expr(&variant.fields);
                size_branches.push(quote! {
                    #name::#variant_ident { .. } | #name::#variant_ident(_*) => #size_expr,
                });

                // 生成serialize()方法的match分支
                let serialize_expr = generate_serialize_expr(variant, name);
                serialize_branches.push(quote! {
                    #name::#variant_ident(..) => #serialize_expr,
                });

                // 生成deserialize()方法的match分支
                let deserialize_expr = generate_deserialize_expr(variant);
                deserialize_arms.push(quote! {
                    #variant_id => #deserialize_expr,
                });
            }

            (
                quote! {
                    fn id(&self) -> usize {
                        match self {
                            #(#id_branches)*
                        }
                    }
                },
                quote! {
                    fn size(&self) -> usize {
                        match self {
                            #(#size_branches)*
                        }
                    }
                },
                quote! {
                    fn serialize(self) -> Vec<u8> {
                        let mut buf = Vec::new();
                        match self {
                            #(#serialize_branches)*
                        }
                        buf
                    }
                },
                quote! {
                    fn deserialize(id: usize, mut data: Vec<u8>) -> Self {
                        use byteorder::{ByteOrder, LittleEndian};
                        match id {
                            #(#deserialize_arms)*
                            _ => panic!("Unknown message id: {}", id),
                        }
                    }
                },
            )
        }
        _ => panic!("NetworkSerializable can only be derived for enums"),
    };

    quote! {
        impl NetworkSerializable for #name {
            #id_impl
            #size_impl
            #serialize_impl
            #deserialize_impl
        }
    }
}

// 生成计算变体大小的表达式
fn generate_size_expr(fields: &Fields) -> TokenStream2 {
    match fields {
        Fields::Unit => quote!(0),
        Fields::Unnamed(fields) => {
            let field_sizes = fields.unnamed.iter().map(|_| {
                quote!(std::mem::size_of::<_>())
            });
            quote!(0 #(+ #field_sizes)*)
        }
        Fields::Named(fields) => {
            let field_sizes = fields.named.iter().map(|_| {
                quote!(std::mem::size_of::<_>())
            });
            quote!(0 #(+ #field_sizes)*)
        }
    }
}

// 生成序列化变体的表达式
fn generate_serialize_expr(variant: &Variant, enum_name: &Ident) -> TokenStream2 {
    let variant_ident = &variant.ident;
    match &variant.fields {
        Fields::Unit => quote! { buf },
        Fields::Unnamed(fields) => {
            let field_names = (0..fields.unnamed.len()).map(|i| {
                Ident::new(&format!("f{}", i), variant.span())
            });
            quote! {
                let #enum_name::#variant_ident(#(#field_names),*) = self;
                #(
                    byteorder::LittleEndian::write_uint(&mut buf, *#field_names as u64, std::mem::size_of::<_>());
                )*
                buf
            }
        }
        Fields::Named(fields) => {
            let field_names = fields.named.iter().map(|f| &f.ident);
            quote! {
                let #enum_name::#variant_ident { #(#field_names),* } = self;
                #(
                    byteorder::LittleEndian::write_uint(&mut buf, *#field_names as u64, std::mem::size_of::<_>());
                )*
                buf
            }
        }
    }
}

// 生成反序列化变体的表达式
fn generate_deserialize_expr(variant: &Variant) -> TokenStream2 {
    let variant_ident = &variant.ident;
    match &variant.fields {
        Fields::Unit => quote! { #variant_ident },
        Fields::Unnamed(fields) => {
            let field_parsers = (0..fields.unnamed.len()).map(|_| {
                quote! {
                    byteorder::LittleEndian::read_uint(&data, std::mem::size_of::<_>()) as _
                }
            });
            quote! {
                #variant_ident(#(#field_parsers),*)
            }
        }
        Fields::Named(fields) => {
            let field_entries = fields.named.iter().map(|f| {
                let ident = &f.ident;
                quote! {
                    #ident: byteorder::LittleEndian::read_uint(&data, std::mem::size_of::<_>()) as _
                }
            });
            quote! {
                #variant_ident { #(#field_entries),* }
            }
        }
    }
}

三、关键修复点说明

  1. Proc宏构建配置:通过proc-macro = true告诉Cargo编译为合法的proc宏动态库,解决核心加载错误
  2. 语法树处理:正确引用syn::Data::Enum,生成符合Rust语法的match分支
  3. 方法完整性:补全了trait中四个方法的生成逻辑,覆盖枚举的单元变体、无名字段变体、命名字段变体
  4. 错误处理:替换不安全的unwrap()为syn::parse_macro_input!,添加非枚举类型的错误提示
  5. 序列化逻辑:使用byteorder实现跨平台的序列化/反序列化,可根据需求替换为bincode等其他方案

四、使用示例

在主项目的Cargo.toml中添加依赖:

[dependencies]
network-serializable-derive = { path = "../network-serializable-derive" }
byteorder = "1.4"

然后在枚举上使用derive:

use network_serializable_derive::NetworkSerializable;
use your_crate::NetworkSerializable;

#[derive(NetworkSerializable)]
enum NetworkMessages {
    ErrorMessage,
    SpawnPlayer(usize, bool, Transform),
    MovePlayer(usize, Transform),
    DestroyPlayer(usize)
}

内容的提问来源于stack exchange,提问作者LucioleMaléfique

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 22:05:44