This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new a570c2f0d feat(rust): add tuple struct support and improve generic 
type handling (#3087)
a570c2f0d is described below

commit a570c2f0d345d6de47f4dfb4c7cb73a285a45825
Author: Damon Zhao <[email protected]>
AuthorDate: Thu Dec 25 12:02:37 2025 +0800

    feat(rust): add tuple struct support and improve generic type handling 
(#3087)
    
    ## Why?
    
    To support serialization of tuple struct and improve generic type in
    Rust.
    
    After this PR, we can finally support fory for
    [OpenRaft](https://github.com/databendlabs/openraft)
    
    ## What does this PR do?
    
    This PR adds tuple struct serialization support and improves generic
    type handling in `fory-derive`.
    
    ### 1. Tuple Struct Support
    
    Previously, `#[derive(ForyObject)]` only supported named structs. Now it
    also supports tuple structs:
    
    ```rust
    use fory_derive::ForyObject;
    
    // Tuple struct with multiple fields
    #[derive(ForyObject, Debug, PartialEq)]
    struct Point(f64, f64);
    
    // Single field wrapper
    #[derive(ForyObject, Debug, PartialEq)]
    struct UserId(u64);
    
    // Complex tuple struct
    #[derive(ForyObject, Debug, PartialEq)]
    struct Record(i32, String, Vec<u8>);
    
    fn main() {
        let mut fory = Fory::default();
        fory.register::<Point>(100).unwrap();
    
        let point = Point(3.5, 4.5);
        let bytes = fory.serialize(&point).unwrap();
        let result: Point = fory.deserialize(&bytes).unwrap();
        assert_eq!(result, point);
    }
    ```
    
    ### 2. Improved Generic Type Handling
    
    - Fixed field ordering for tuple structs (must preserve original order,
    not sort by type)
    - Better fingerprint computation for struct versioning
    - Improved type parameter detection in generic types
    
    ## Related issues
    
    None
    
    ## Does this PR introduce any user-facing change?
    
    - [x] Does this PR introduce any public API change?
      - Yes, adds new public types support: tuple struct
    - [ ] Does this PR introduce any binary protocol compatibility change?
      - No
    
    ## Benchmark
---
 rust/fory-derive/src/lib.rs                |  24 ++-
 rust/fory-derive/src/object/derive_enum.rs |  11 +-
 rust/fory-derive/src/object/misc.rs        | 100 +++++-----
 rust/fory-derive/src/object/read.rs        | 131 ++++++++-----
 rust/fory-derive/src/object/serializer.rs  | 144 ++++++++------
 rust/fory-derive/src/object/util.rs        | 121 +++++++++++-
 rust/fory-derive/src/object/write.rs       |  39 ++--
 rust/fory-derive/src/util.rs               |  16 +-
 rust/tests/tests/test_associated_types.rs  | 147 ++++++++++++++
 rust/tests/tests/test_tuple_struct.rs      | 300 +++++++++++++++++++++++++++++
 10 files changed, 849 insertions(+), 184 deletions(-)

diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs
index 69a335ba7..017eb6f12 100644
--- a/rust/fory-derive/src/lib.rs
+++ b/rust/fory-derive/src/lib.rs
@@ -216,12 +216,12 @@ pub fn proc_macro_derive_fory_object(input: 
proc_macro::TokenStream) -> TokenStr
 
     // Check if this is being applied to a trait (which is not possible with 
derive macros)
     // Derive macros can only be applied to structs, enums, and unions
-    let (debug_enabled, generate_default) = match 
parse_fory_attrs(&input.attrs) {
-        Ok(flags) => flags,
+    let attrs = match parse_fory_attrs(&input.attrs) {
+        Ok(attrs) => attrs,
         Err(err) => return err.into_compile_error().into(),
     };
 
-    object::derive_serializer(&input, debug_enabled, generate_default)
+    object::derive_serializer(&input, attrs)
 }
 
 /// Derive macro for row-based serialization.
@@ -249,8 +249,14 @@ pub fn proc_macro_derive_fory_row(input: 
proc_macro::TokenStream) -> TokenStream
     derive_row(&input)
 }
 
-/// Parse fory attributes and return (debug_enabled, generate_default)
-fn parse_fory_attrs(attrs: &[Attribute]) -> syn::Result<(bool, bool)> {
+/// Parsed fory attributes
+pub(crate) struct ForyAttrs {
+    pub debug_enabled: bool,
+    pub generate_default: bool,
+}
+
+/// Parse fory attributes and return ForyAttrs
+fn parse_fory_attrs(attrs: &[Attribute]) -> syn::Result<ForyAttrs> {
     let mut debug_flag: Option<bool> = None;
     let mut generate_default_flag: Option<bool> = None;
 
@@ -297,8 +303,8 @@ fn parse_fory_attrs(attrs: &[Attribute]) -> 
syn::Result<(bool, bool)> {
         }
     }
 
-    Ok((
-        debug_flag.unwrap_or(false),
-        generate_default_flag.unwrap_or(false),
-    ))
+    Ok(ForyAttrs {
+        debug_enabled: debug_flag.unwrap_or(false),
+        generate_default: generate_default_flag.unwrap_or(false),
+    })
 }
diff --git a/rust/fory-derive/src/object/derive_enum.rs 
b/rust/fory-derive/src/object/derive_enum.rs
index e32fae431..6261c5f9e 100644
--- a/rust/fory-derive/src/object/derive_enum.rs
+++ b/rust/fory-derive/src/object/derive_enum.rs
@@ -488,8 +488,12 @@ fn rust_variant_read_branches(
                     let read_fields: Vec<TokenStream> = fields_unnamed
                         .unnamed
                         .iter()
+                        .enumerate()
                         .zip(field_idents.iter())
-                        .map(|(f, ident)| gen_read_field(f, ident))
+                        .map(|((idx, f), ident)| {
+                            let field_name = idx.to_string();
+                            gen_read_field(f, ident, &field_name)
+                        })
                         .collect();
 
                     quote! {
@@ -511,7 +515,10 @@ fn rust_variant_read_branches(
                     let read_fields: Vec<_> = sorted_fields
                         .iter()
                         .zip(field_idents.iter())
-                        .map(|(f, ident)| gen_read_field(f, ident))
+                        .map(|(f, ident)| {
+                            let field_name = ident.to_string();
+                            gen_read_field(f, ident, &field_name)
+                        })
                         .collect();
 
                     quote! {
diff --git a/rust/fory-derive/src/object/misc.rs 
b/rust/fory-derive/src/object/misc.rs
index bcca22605..c0e5a18ba 100644
--- a/rust/fory-derive/src/object/misc.rs
+++ b/rust/fory-derive/src/object/misc.rs
@@ -35,9 +35,9 @@ pub fn allocate_type_id() -> u32 {
 
 #[allow(dead_code)]
 fn hash(fields: &[&Field]) -> TokenStream {
-    let props = fields.iter().map(|field| {
+    let props = fields.iter().enumerate().map(|(idx, field)| {
         let ty = &field.ty;
-        let name = format!("{}", field.ident.as_ref().expect("should be field 
name"));
+        let name = super::util::get_field_name(field, idx);
         quote! {
             (#name, <#ty as 
fory_core::serializer::Serializer>::fory_get_type_id())
         }
@@ -72,61 +72,63 @@ pub fn gen_get_sorted_field_names(fields: &[&Field]) -> 
TokenStream {
 }
 
 pub fn gen_field_fields_info(fields: &[&Field]) -> TokenStream {
-    let field_infos = get_filtered_fields_iter(fields).map(|field| {
-        let ty = &field.ty;
-        let name = format!("{}", field.ident.as_ref().expect("should be field 
name"));
-        match classify_trait_object_field(ty) {
-            StructField::None => {
-                let generic_tree = parse_generic_tree(ty);
-                let generic_token = generic_tree_to_tokens(&generic_tree);
-                quote! {
-                    fory_core::meta::FieldInfo::new(#name, #generic_token)
+    let field_infos = get_filtered_fields_iter(fields)
+        .enumerate()
+        .map(|(idx, field)| {
+            let ty = &field.ty;
+            let name = super::util::get_field_name(field, idx);
+            match classify_trait_object_field(ty) {
+                StructField::None => {
+                    let generic_tree = parse_generic_tree(ty);
+                    let generic_token = generic_tree_to_tokens(&generic_tree);
+                    quote! {
+                        fory_core::meta::FieldInfo::new(#name, #generic_token)
+                    }
                 }
-            }
-            StructField::VecBox(_) | StructField::VecRc(_) | 
StructField::VecArc(_) => {
-                quote! {
-                    fory_core::meta::FieldInfo::new(#name, 
fory_core::meta::FieldType {
-                        type_id: fory_core::types::TypeId::LIST as u32,
-                        nullable: false,
-                        generics: vec![fory_core::meta::FieldType {
-                            type_id: fory_core::types::TypeId::UNKNOWN as u32,
+                StructField::VecBox(_) | StructField::VecRc(_) | 
StructField::VecArc(_) => {
+                    quote! {
+                        fory_core::meta::FieldInfo::new(#name, 
fory_core::meta::FieldType {
+                            type_id: fory_core::types::TypeId::LIST as u32,
                             nullable: false,
-                            generics: Vec::new()
-                        }]
-                    })
-                }
-            }
-            StructField::HashMapBox(key_ty, _)
-            | StructField::HashMapRc(key_ty, _)
-            | StructField::HashMapArc(key_ty, _) => {
-                let key_generic_tree = parse_generic_tree(key_ty.as_ref());
-                let key_generic_token = 
generic_tree_to_tokens(&key_generic_tree);
-                quote! {
-                    fory_core::meta::FieldInfo::new(#name, 
fory_core::meta::FieldType {
-                        type_id: fory_core::types::TypeId::MAP as u32,
-                        nullable: false,
-                        generics: vec![
-                            #key_generic_token,
-                            fory_core::meta::FieldType {
+                            generics: vec![fory_core::meta::FieldType {
                                 type_id: fory_core::types::TypeId::UNKNOWN as 
u32,
                                 nullable: false,
                                 generics: Vec::new()
-                            }
-                        ]
-                    })
+                            }]
+                        })
+                    }
                 }
-            }
-            _ => {
-                quote! {
-                    fory_core::meta::FieldInfo::new(#name, 
fory_core::meta::FieldType {
-                        type_id: fory_core::types::TypeId::UNKNOWN as u32,
-                        nullable: false,
-                        generics: Vec::new()
-                    })
+                StructField::HashMapBox(key_ty, _)
+                | StructField::HashMapRc(key_ty, _)
+                | StructField::HashMapArc(key_ty, _) => {
+                    let key_generic_tree = parse_generic_tree(key_ty.as_ref());
+                    let key_generic_token = 
generic_tree_to_tokens(&key_generic_tree);
+                    quote! {
+                        fory_core::meta::FieldInfo::new(#name, 
fory_core::meta::FieldType {
+                            type_id: fory_core::types::TypeId::MAP as u32,
+                            nullable: false,
+                            generics: vec![
+                                #key_generic_token,
+                                fory_core::meta::FieldType {
+                                    type_id: fory_core::types::TypeId::UNKNOWN 
as u32,
+                                    nullable: false,
+                                    generics: Vec::new()
+                                }
+                            ]
+                        })
+                    }
+                }
+                _ => {
+                    quote! {
+                        fory_core::meta::FieldInfo::new(#name, 
fory_core::meta::FieldType {
+                            type_id: fory_core::types::TypeId::UNKNOWN as u32,
+                            nullable: false,
+                            generics: Vec::new()
+                        })
+                    }
                 }
             }
-        }
-    });
+        });
 
     // Get sorted field names for sorting
     let static_field_names = get_sort_fields_ts(fields);
diff --git a/rust/fory-derive/src/object/read.rs 
b/rust/fory-derive/src/object/read.rs
index e53a32789..349db4167 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -26,8 +26,14 @@ use super::util::{
     should_skip_type_info_for_field, skip_ref_flag, StructField,
 };
 
-pub(crate) fn create_private_field_name(field: &Field) -> Ident {
-    format_ident!("_{}", field.ident.as_ref().unwrap())
+/// Create a private variable name for a field during deserialization.
+/// For named fields: `_field_name`
+/// For tuple struct fields: `_0`, `_1`, etc.
+pub(crate) fn create_private_field_name(field: &Field, index: usize) -> Ident {
+    match &field.ident {
+        Some(ident) => format_ident!("_{}", ident),
+        None => format_ident!("_{}", index),
+    }
 }
 
 fn need_declared_by_option(field: &Field) -> bool {
@@ -38,9 +44,10 @@ fn need_declared_by_option(field: &Field) -> bool {
 pub(crate) fn declare_var(fields: &[&Field]) -> Vec<TokenStream> {
     fields
         .iter()
-        .map(|field| {
+        .enumerate()
+        .map(|(idx, field)| {
             let ty = &field.ty;
-            let var_name = create_private_field_name(field);
+            let var_name = create_private_field_name(field, idx);
             match classify_trait_object_field(ty) {
                 StructField::BoxDyn
                 | StructField::RcDyn(_)
@@ -70,40 +77,43 @@ pub(crate) fn declare_var(fields: &[&Field]) -> 
Vec<TokenStream> {
 }
 
 pub(crate) fn assign_value(fields: &[&Field]) -> Vec<TokenStream> {
+    let is_tuple = super::util::is_tuple_struct(fields);
+
     fields
         .iter()
-        .map(|field| {
-            let name = &field.ident;
-            let var_name = create_private_field_name(field);
-            match classify_trait_object_field(&field.ty) {
+        .enumerate()
+        .map(|(idx, field)| {
+            let var_name = create_private_field_name(field, idx);
+            let value_expr = match classify_trait_object_field(&field.ty) {
                 StructField::BoxDyn | StructField::RcDyn(_) | 
StructField::ArcDyn(_) => {
-                    quote! {
-                        #name: #var_name
-                    }
+                    quote! { #var_name }
                 }
                 StructField::ContainsTraitObject => {
-                    quote! {
-                        #name: #var_name.unwrap()
-                    }
+                    quote! { #var_name.unwrap() }
                 }
                 _ => {
                     if need_declared_by_option(field) {
                         let ty = &field.ty;
-                        quote! {
-                            #name: #var_name.unwrap_or_else(|| <#ty as 
fory_core::ForyDefault>::fory_default())
-                        }
+                        quote! { #var_name.unwrap_or_else(|| <#ty as 
fory_core::ForyDefault>::fory_default()) }
                     } else {
-                        quote! {
-                            #name: #var_name
-                        }
+                        quote! { #var_name }
                     }
                 }
+            };
+
+            if is_tuple {
+                // For tuple structs, just return the value
+                value_expr
+            } else {
+                // For named structs, include the field name
+                let name = &field.ident;
+                quote! { #name: #value_expr }
             }
         })
         .collect()
 }
 
-pub fn gen_read_field(field: &Field, private_ident: &Ident) -> TokenStream {
+pub fn gen_read_field(field: &Field, private_ident: &Ident, field_name: &str) 
-> TokenStream {
     let ty = &field.ty;
     if is_skip_field(field) {
         return quote! {
@@ -232,8 +242,7 @@ pub fn gen_read_field(field: &Field, private_ident: &Ident) 
-> TokenStream {
     if is_debug_enabled() {
         let struct_name = get_struct_name().expect("struct context not set");
         let struct_name_lit = syn::LitStr::new(&struct_name, 
proc_macro2::Span::call_site());
-        let field_name = field.ident.as_ref().unwrap().to_string();
-        let field_name_lit = syn::LitStr::new(&field_name, 
proc_macro2::Span::call_site());
+        let field_name_lit = syn::LitStr::new(field_name, 
proc_macro2::Span::call_site());
         quote! {
             fory_core::serializer::struct_::struct_before_read_field(
                 #struct_name_lit,
@@ -262,9 +271,11 @@ pub fn gen_read_type_info() -> TokenStream {
 fn get_fields_loop_ts(fields: &[&Field]) -> TokenStream {
     let read_fields_ts: Vec<_> = fields
         .iter()
-        .map(|field| {
-            let private_ident = create_private_field_name(field);
-            gen_read_field(field, &private_ident)
+        .enumerate()
+        .map(|(idx, field)| {
+            let private_ident = create_private_field_name(field, idx);
+            let field_name = super::util::get_field_name(field, idx);
+            gen_read_field(field, &private_ident, &field_name)
         })
         .collect();
     quote! {
@@ -282,13 +293,29 @@ pub fn gen_read_data(fields: &[&Field]) -> TokenStream {
             #loop_ts
         }
     };
-    let field_idents = fields.iter().map(|field| {
-        let private_ident = create_private_field_name(field);
-        let original_ident = &field.ident;
-        quote! {
-            #original_ident: #private_ident
-        }
-    });
+
+    let is_tuple = super::util::is_tuple_struct(fields);
+    let field_idents: Vec<_> = fields
+        .iter()
+        .enumerate()
+        .map(|(idx, field)| {
+            let private_ident = create_private_field_name(field, idx);
+            if is_tuple {
+                // For tuple structs, just use the variable
+                quote! { #private_ident }
+            } else {
+                // For named structs, include the field name
+                let original_ident = &field.ident;
+                quote! { #original_ident: #private_ident }
+            }
+        })
+        .collect();
+    let self_construction = if is_tuple {
+        quote! { Ok(Self( #(#field_idents),* )) }
+    } else {
+        quote! { Ok(Self { #(#field_idents),* }) }
+    };
+
     quote! {
         // Read and check version hash when class version checking is enabled
         if context.is_check_struct_version() {
@@ -297,13 +324,15 @@ pub fn gen_read_data(fields: &[&Field]) -> TokenStream {
             fory_core::meta::TypeMeta::check_struct_version(read_version, 
#version_hash, type_name)?;
         }
         #sorted_read
-        Ok(Self {
-            #(#field_idents),*
-        })
+        #self_construction
     }
 }
 
-pub(crate) fn gen_read_compatible_match_arm_body(field: &Field, var_name: 
&Ident) -> TokenStream {
+pub(crate) fn gen_read_compatible_match_arm_body(
+    field: &Field,
+    var_name: &Ident,
+    field_name: &str,
+) -> TokenStream {
     let ty = &field.ty;
     let field_kind = classify_trait_object_field(ty);
     let is_skip_flag = is_skip_field(field);
@@ -482,8 +511,7 @@ pub(crate) fn gen_read_compatible_match_arm_body(field: 
&Field, var_name: &Ident
     if is_debug_enabled() {
         let struct_name = get_struct_name().expect("struct context not set");
         let struct_name_lit = syn::LitStr::new(&struct_name, 
proc_macro2::Span::call_site());
-        let field_name = field.ident.as_ref().unwrap().to_string();
-        let field_name_lit = syn::LitStr::new(&field_name, 
proc_macro2::Span::call_site());
+        let field_name_lit = syn::LitStr::new(field_name, 
proc_macro2::Span::call_site());
         quote! {
             fory_core::serializer::struct_::struct_before_read_field(
                 #struct_name_lit,
@@ -505,7 +533,10 @@ pub(crate) fn gen_read_compatible_match_arm_body(field: 
&Field, var_name: &Ident
     }
 }
 
-pub fn gen_read(struct_ident: &Ident) -> TokenStream {
+pub fn gen_read(_struct_ident: &Ident) -> TokenStream {
+    // Note: We use `Self` instead of `#struct_ident` to correctly handle 
generic types.
+    // When the struct has generics (e.g., LeaderId<C>), using `Self` ensures 
the full
+    // type with generics is used in the impl block.
     quote! {
         let ref_flag = if read_ref_info {
             context.reader.read_i8()?
@@ -520,7 +551,7 @@ pub fn gen_read(struct_ident: &Ident) -> TokenStream {
                     let rs_type_id = std::any::TypeId::of::<Self>();
                     context.get_type_info(&rs_type_id)?
                 };
-                <#struct_ident as 
fory_core::StructSerializer>::fory_read_compatible(context, type_info)
+                <Self as 
fory_core::StructSerializer>::fory_read_compatible(context, type_info)
             } else {
                 if read_type_info {
                     <Self as 
fory_core::Serializer>::fory_read_type_info(context)?;
@@ -535,12 +566,13 @@ pub fn gen_read(struct_ident: &Ident) -> TokenStream {
     }
 }
 
-pub fn gen_read_with_type_info(struct_ident: &Ident) -> TokenStream {
+pub fn gen_read_with_type_info() -> TokenStream {
     // fn fory_read_with_type_info(
     //     context: &mut ReadContext,
     //     read_ref_info: bool,
     //     type_info: Rc<TypeInfo>,
     // ) -> Result<Self, Error>
+    // Note: We use `Self` instead of `#struct_ident` to correctly handle 
generic types.
     quote! {
         let ref_flag = if read_ref_info {
             context.reader.read_i8()?
@@ -549,7 +581,7 @@ pub fn gen_read_with_type_info(struct_ident: &Ident) -> 
TokenStream {
         };
         if ref_flag == (fory_core::RefFlag::NotNullValue as i8) || ref_flag == 
(fory_core::RefFlag::RefValue as i8) {
             if context.is_compatible() {
-                <#struct_ident as 
fory_core::StructSerializer>::fory_read_compatible(context, type_info)
+                <Self as 
fory_core::StructSerializer>::fory_read_compatible(context, type_info)
             } else {
                 <Self as fory_core::Serializer>::fory_read_data(context)
             }
@@ -576,9 +608,10 @@ pub(crate) fn gen_read_compatible_with_construction(
         .iter()
         .enumerate()
         .map(|(i, field)| {
-            let var_name = create_private_field_name(field);
+            let var_name = create_private_field_name(field, i);
+            let field_name = super::util::get_field_name(field, i);
             let field_id = i as i16;
-            let body = gen_read_compatible_match_arm_body(field, &var_name);
+            let body = gen_read_compatible_match_arm_body(field, &var_name, 
&field_name);
             quote! {
                 #field_id => {
                     #body
@@ -627,13 +660,21 @@ pub(crate) fn gen_read_compatible_with_construction(
     };
 
     // Generate construction based on whether this is a struct or enum variant
+    let is_tuple = super::util::is_tuple_struct(fields);
     let construction = if let Some(variant) = variant_ident {
+        // Enum variants use named syntax (struct variants) or tuple syntax 
(tuple variants)
         quote! {
             Ok(Self::#variant {
                 #(#assign_ts),*
             })
         }
+    } else if is_tuple {
+        // Tuple structs use parentheses
+        quote! {
+            Ok(Self( #(#assign_ts),* ))
+        }
     } else {
+        // Named structs use braces
         quote! {
             Ok(Self {
                 #(#assign_ts),*
diff --git a/rust/fory-derive/src/object/serializer.rs 
b/rust/fory-derive/src/object/serializer.rs
index 374745392..e221a0a4d 100644
--- a/rust/fory-derive/src/object/serializer.rs
+++ b/rust/fory-derive/src/object/serializer.rs
@@ -17,6 +17,7 @@
 
 use crate::object::{derive_enum, misc, read, write};
 use crate::util::sorted_fields;
+use crate::ForyAttrs;
 use proc_macro::TokenStream;
 use quote::quote;
 use syn::Data;
@@ -36,19 +37,28 @@ fn has_existing_default(ast: &syn::DeriveInput, trait_name: 
&str) -> bool {
     })
 }
 
-pub fn derive_serializer(
-    ast: &syn::DeriveInput,
-    debug_enabled: bool,
-    generate_default: bool,
-) -> TokenStream {
+pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> 
TokenStream {
     let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = 
ast.generics.split_for_impl();
+
+    // Extract type parameter names from generics (e.g., "C", "T", "E")
+    let type_params: std::collections::HashSet<String> = ast
+        .generics
+        .params
+        .iter()
+        .filter_map(|p| match p {
+            syn::GenericParam::Type(tp) => Some(tp.ident.to_string()),
+            _ => None, // Ignore lifetime and const parameters
+        })
+        .collect();
+
     use crate::object::util::{clear_struct_context, set_struct_context};
-    set_struct_context(&name.to_string(), debug_enabled);
+    set_struct_context(&name.to_string(), attrs.debug_enabled, type_params);
 
     // Check if ForyDefault is already derived/implemented
     let has_existing_default = has_existing_default(ast, "ForyDefault");
     let default_impl = if !has_existing_default {
-        generate_default_impl(ast, generate_default)
+        generate_default_impl(ast, attrs.generate_default)
     } else {
         quote! {}
     };
@@ -112,7 +122,7 @@ pub fn derive_serializer(
                 write::gen_write_data(&fields),
                 write::gen_write_type_info(),
                 read::gen_read(name),
-                read::gen_read_with_type_info(name),
+                read::gen_read_with_type_info(),
                 read::gen_read_data(&fields),
                 read::gen_read_type_info(),
                 write::gen_reserved_space(&fields),
@@ -146,7 +156,7 @@ pub fn derive_serializer(
 
         #default_impl
 
-        impl fory_core::StructSerializer for #name {
+        impl #impl_generics fory_core::StructSerializer for #name #ty_generics 
#where_clause {
             #[inline(always)]
             fn fory_type_index() -> u32 {
                 #type_idx
@@ -175,7 +185,7 @@ pub fn derive_serializer(
             }
         }
 
-        impl fory_core::Serializer for #name {
+        impl #impl_generics fory_core::Serializer for #name #ty_generics 
#where_clause {
             #[inline(always)]
             fn fory_get_type_id(type_resolver: 
&fory_core::resolver::type_resolver::TypeResolver) -> Result<u32, 
fory_core::error::Error> {
                 type_resolver.get_type_id(&std::any::TypeId::of::<Self>(), 
#type_idx)
@@ -251,6 +261,8 @@ fn generate_default_impl(
     generate_default: bool,
 ) -> proc_macro2::TokenStream {
     let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = 
ast.generics.split_for_impl();
+
     // By default, we don't generate Default impl to avoid conflicts.
     // Only generate if generate_default is true AND there's no existing 
Default.
     let should_generate_default = generate_default && 
!has_existing_default(ast, "Default");
@@ -258,63 +270,87 @@ fn generate_default_impl(
     match &ast.data {
         Data::Struct(s) => {
             let fields = sorted_fields(&s.fields);
+            let is_tuple_struct = super::util::is_tuple_struct(&fields);
 
             use super::util::{
                 classify_trait_object_field, create_wrapper_types_arc, 
create_wrapper_types_rc,
                 StructField,
             };
 
-            let field_inits = fields.iter().map(|field| {
-                let ident = &field.ident;
-                let ty = &field.ty;
-
-                match classify_trait_object_field(ty) {
-                    StructField::RcDyn(trait_name) => {
-                        let types = create_wrapper_types_rc(&trait_name);
-                        let wrapper_ty = types.wrapper_ty;
-                        let trait_ident = types.trait_ident;
-                        quote! {
-                            #ident: {
-                                let wrapper = #wrapper_ty::default();
-                                std::rc::Rc::<dyn #trait_ident>::from(wrapper)
+            let field_inits: Vec<_> = fields
+                .iter()
+                .map(|field| {
+                    let ident = &field.ident;
+                    let ty = &field.ty;
+
+                    match classify_trait_object_field(ty) {
+                        StructField::RcDyn(trait_name) => {
+                            let types = create_wrapper_types_rc(&trait_name);
+                            let wrapper_ty = types.wrapper_ty;
+                            let trait_ident = types.trait_ident;
+                            let value = quote! {
+                                {
+                                    let wrapper = #wrapper_ty::default();
+                                    std::rc::Rc::<dyn 
#trait_ident>::from(wrapper)
+                                }
+                            };
+                            if is_tuple_struct {
+                                value
+                            } else {
+                                quote! { #ident: #value }
                             }
                         }
-                    }
-                    StructField::ArcDyn(trait_name) => {
-                        let types = create_wrapper_types_arc(&trait_name);
-                        let wrapper_ty = types.wrapper_ty;
-                        let trait_ident = types.trait_ident;
-                        quote! {
-                            #ident: {
-                                let wrapper = #wrapper_ty::default();
-                                std::sync::Arc::<dyn 
#trait_ident>::from(wrapper)
+                        StructField::ArcDyn(trait_name) => {
+                            let types = create_wrapper_types_arc(&trait_name);
+                            let wrapper_ty = types.wrapper_ty;
+                            let trait_ident = types.trait_ident;
+                            let value = quote! {
+                                {
+                                    let wrapper = #wrapper_ty::default();
+                                    std::sync::Arc::<dyn 
#trait_ident>::from(wrapper)
+                                }
+                            };
+                            if is_tuple_struct {
+                                value
+                            } else {
+                                quote! { #ident: #value }
                             }
                         }
-                    }
-                    StructField::Forward => {
-                        quote! {
-                            #ident: <#ty as 
fory_core::ForyDefault>::fory_default()
+                        StructField::Forward => {
+                            let value = quote! { <#ty as 
fory_core::ForyDefault>::fory_default() };
+                            if is_tuple_struct {
+                                value
+                            } else {
+                                quote! { #ident: #value }
+                            }
                         }
-                    }
-                    _ => {
-                        quote! {
-                            #ident: <#ty as 
fory_core::ForyDefault>::fory_default()
+                        _ => {
+                            let value = quote! { <#ty as 
fory_core::ForyDefault>::fory_default() };
+                            if is_tuple_struct {
+                                value
+                            } else {
+                                quote! { #ident: #value }
+                            }
                         }
                     }
-                }
-            });
+                })
+                .collect();
+
+            let self_construction = if is_tuple_struct {
+                quote! { Self( #(#field_inits),* ) }
+            } else {
+                quote! { Self { #(#field_inits),* } }
+            };
 
             if should_generate_default {
                 // User requested Default generation via 
#[fory(generate_default)]
                 quote! {
-                    impl fory_core::ForyDefault for #name {
+                    impl #impl_generics fory_core::ForyDefault for #name 
#ty_generics #where_clause {
                         fn fory_default() -> Self {
-                            Self {
-                                #(#field_inits),*
-                            }
+                            #self_construction
                         }
                     }
-                    impl std::default::Default for #name {
+                    impl #impl_generics std::default::Default for #name 
#ty_generics #where_clause {
                         fn default() -> Self {
                             Self::fory_default()
                         }
@@ -324,11 +360,9 @@ fn generate_default_impl(
                 // Default case: only generate ForyDefault, not Default
                 // This avoids conflicts with existing Default implementations
                 quote! {
-                   impl fory_core::ForyDefault for #name {
+                   impl #impl_generics fory_core::ForyDefault for #name 
#ty_generics #where_clause {
                         fn fory_default() -> Self {
-                            Self {
-                                #(#field_inits),*
-                            }
+                            #self_construction
                         }
                     }
                 }
@@ -369,7 +403,7 @@ fn generate_default_impl(
                     // User has #[derive(Default)] or #[default] attribute
                     // Only generate ForyDefault that delegates to Default
                     quote! {
-                        impl fory_core::ForyDefault for #name {
+                        impl #impl_generics fory_core::ForyDefault for #name 
#ty_generics #where_clause {
                             fn fory_default() -> Self {
                                 Self::default()
                             }
@@ -378,13 +412,13 @@ fn generate_default_impl(
                 } else if should_generate_default {
                     // User requested Default generation via 
#[fory(generate_default)]
                     quote! {
-                        impl fory_core::ForyDefault for #name {
+                        impl #impl_generics fory_core::ForyDefault for #name 
#ty_generics #where_clause {
                             fn fory_default() -> Self {
                                 Self::#variant_ident #field_defaults
                             }
                         }
 
-                        impl std::default::Default for #name {
+                        impl #impl_generics std::default::Default for #name 
#ty_generics #where_clause {
                             fn default() -> Self {
                                 Self::#variant_ident #field_defaults
                             }
@@ -393,7 +427,7 @@ fn generate_default_impl(
                 } else {
                     // Default case: only generate ForyDefault, not Default
                     quote! {
-                        impl fory_core::ForyDefault for #name {
+                        impl #impl_generics fory_core::ForyDefault for #name 
#ty_generics #where_clause {
                             fn fory_default() -> Self {
                                 Self::#variant_ident #field_defaults
                             }
diff --git a/rust/fory-derive/src/object/util.rs 
b/rust/fory-derive/src/object/util.rs
index 1389dfeb9..fbd9e8c60 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -26,7 +26,41 @@ use quote::{format_ident, quote, ToTokens};
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
-use syn::{Field, GenericArgument, PathArguments, Type};
+use syn::{Field, GenericArgument, Index, PathArguments, Type};
+
+/// Get field name for a field, handling both named and tuple struct fields.
+/// For named fields, returns the field name.
+/// For tuple struct fields, returns the index as a string (e.g., "0", "1").
+pub(super) fn get_field_name(field: &Field, index: usize) -> String {
+    match &field.ident {
+        Some(ident) => ident.to_string(),
+        None => index.to_string(),
+    }
+}
+
+/// Get the field accessor token for a field.
+/// For named fields: `self.field_name`
+/// For tuple struct fields: `self.0`, `self.1`, etc.
+pub(super) fn get_field_accessor(field: &Field, index: usize, use_self: bool) 
-> TokenStream {
+    let prefix = if use_self {
+        quote! { self. }
+    } else {
+        quote! {}
+    };
+
+    match &field.ident {
+        Some(ident) => quote! { #prefix #ident },
+        None => {
+            let idx = Index::from(index);
+            quote! { #prefix #idx }
+        }
+    }
+}
+
+/// Check if this is a tuple struct (all fields are unnamed)
+pub fn is_tuple_struct(fields: &[&Field]) -> bool {
+    !fields.is_empty() && fields[0].ident.is_none()
+}
 
 thread_local! {
     static MACRO_CONTEXT: RefCell<Option<MacroContext>> = const 
{RefCell::new(None)};
@@ -36,13 +70,24 @@ thread_local! {
 struct MacroContext {
     struct_name: String,
     debug_enabled: bool,
+    /// Type parameter names extracted from the struct/enum generics (e.g., 
"C", "T", "E")
+    type_params: std::collections::HashSet<String>,
 }
 
-pub(super) fn set_struct_context(name: &str, debug_enabled: bool) {
+/// Set the macro context with struct name, debug flag, and type parameters.
+///
+/// `type_params` should contain the names of all type parameters from the 
struct/enum
+/// generics (e.g., for `struct Vote<C: RaftTypeConfig>`, pass `{"C"}`).
+pub(super) fn set_struct_context(
+    name: &str,
+    debug_enabled: bool,
+    type_params: std::collections::HashSet<String>,
+) {
     MACRO_CONTEXT.with(|ctx| {
         *ctx.borrow_mut() = Some(MacroContext {
             struct_name: name.to_string(),
             debug_enabled,
+            type_params,
         });
     });
 }
@@ -66,6 +111,16 @@ pub(super) fn is_debug_enabled() -> bool {
     })
 }
 
+/// Check if a type name is a type parameter of the current struct/enum.
+pub(super) fn is_type_parameter(name: &str) -> bool {
+    MACRO_CONTEXT.with(|ctx| {
+        ctx.borrow()
+            .as_ref()
+            .map(|c| c.type_params.contains(name))
+            .unwrap_or(false)
+    })
+}
+
 pub(super) fn contains_trait_object(ty: &Type) -> bool {
     match ty {
         Type::TraitObject(_) => true,
@@ -233,7 +288,10 @@ pub(super) fn classify_trait_object_field(ty: &Type) -> 
StructField {
 
 #[derive(Debug)]
 pub(super) struct TypeNode {
+    /// Simple type name, used for type matching (e.g., "LeaderId", "Vec")
     pub name: String,
+    /// Full type path string, used for code generation (e.g., "C::LeaderId")
+    pub full_path: String,
     pub generics: Vec<TypeNode>,
     /// For arrays, store the original type string "[T; N]" to preserve length 
info
     pub original_type_str: Option<String>,
@@ -294,12 +352,14 @@ impl fmt::Display for TypeNode {
                 write!(f, "Array")
             }
         } else if self.generics.is_empty() {
-            write!(f, "{}", self.name)
+            // Use full_path to preserve associated type paths like C::LeaderId
+            write!(f, "{}", self.full_path)
         } else {
+            // Use full_path for the base type, recursively format generics
             write!(
                 f,
                 "{}<{}>",
-                self.name,
+                self.full_path,
                 self.generics
                     .iter()
                     .map(|g| g.to_string())
@@ -324,6 +384,28 @@ pub(super) fn extract_type_name(ty: &Type) -> String {
     }
 }
 
+/// Extracts the full type path string, preserving associated types like 
`C::LeaderId`
+pub(super) fn extract_full_type_path(ty: &Type) -> String {
+    if let Type::Path(type_path) = ty {
+        // Build the full path from all segments without generic arguments
+        type_path
+            .path
+            .segments
+            .iter()
+            .map(|seg| seg.ident.to_string())
+            .collect::<Vec<_>>()
+            .join("::")
+    } else if matches!(ty, Type::TraitObject(_)) {
+        "TraitObject".to_string()
+    } else if matches!(ty, Type::Tuple(_)) {
+        "Tuple".to_string()
+    } else if matches!(ty, Type::Array(_)) {
+        "Array".to_string()
+    } else {
+        quote!(#ty).to_string()
+    }
+}
+
 #[allow(dead_code)]
 pub(super) fn is_option(ty: &Type) -> bool {
     if let Type::Path(type_path) = ty {
@@ -341,6 +423,7 @@ pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
     if matches!(ty, Type::TraitObject(_)) {
         return TypeNode {
             name: "TraitObject".to_string(),
+            full_path: "TraitObject".to_string(),
             generics: vec![],
             original_type_str: None,
         };
@@ -350,6 +433,7 @@ pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
     if let Type::Tuple(_tuple) = ty {
         return TypeNode {
             name: "Tuple".to_string(),
+            full_path: "Tuple".to_string(),
             generics: vec![],
             original_type_str: None,
         };
@@ -362,12 +446,14 @@ pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
         let original_type_str = quote!(#ty).to_string().replace(' ', "");
         return TypeNode {
             name: "Array".to_string(),
+            full_path: "Array".to_string(),
             generics: vec![elem_node],
             original_type_str: Some(original_type_str),
         };
     }
 
     let name = extract_type_name(ty);
+    let full_path = extract_full_type_path(ty);
 
     let generics = if let Type::Path(type_path) = ty {
         if let PathArguments::AngleBracketed(args) =
@@ -391,12 +477,27 @@ pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
     };
     TypeNode {
         name,
+        full_path,
         generics,
         original_type_str: None,
     }
 }
 
 pub(super) fn generic_tree_to_tokens(node: &TypeNode) -> TokenStream {
+    // Special handling for type parameters (e.g., C, T, E)
+    // Type parameters should use UNKNOWN type ID since they are not concrete 
types.
+    // This prevents `C::LeaderId` generating code like `<C as 
Serializer>::fory_get_type_id()` which
+    // would require the type parameter to implement Serializer.
+    if is_type_parameter(&node.name) {
+        return quote! {
+            fory_core::meta::FieldType::new(
+                fory_core::types::TypeId::UNKNOWN as u32,
+                true,
+                vec![]
+            )
+        };
+    }
+
     // Special handling for tuples: always use FieldType { LIST, nullable: 
true, generics: vec![UNKNOWN] }
     if node.name == "Tuple" {
         return quote! {
@@ -817,9 +918,9 @@ fn group_fields_by_type(fields: &[&Field]) -> FieldGroups {
     let mut other_fields = Vec::new();
 
     // First handle Forward fields separately to avoid borrow checker issues
-    for field in fields {
+    for (idx, field) in fields.iter().enumerate() {
         if is_forward_field(&field.ty) {
-            let raw_ident = field.ident.as_ref().unwrap().to_string();
+            let raw_ident = get_field_name(field, idx);
             let ident = to_snake_case(&raw_ident);
             other_fields.push((ident, "Forward".to_string(), TypeId::UNKNOWN 
as u32));
         }
@@ -844,8 +945,8 @@ fn group_fields_by_type(fields: &[&Field]) -> FieldGroups {
         }
     };
 
-    for field in fields {
-        let raw_ident = field.ident.as_ref().unwrap().to_string();
+    for (idx, field) in fields.iter().enumerate() {
+        let raw_ident = get_field_name(field, idx);
         let ident = to_snake_case(&raw_ident);
 
         // Skip if already handled as Forward field
@@ -1013,8 +1114,8 @@ fn to_snake_case(name: &str) -> String {
 /// This format is consistent across Go, Java, Rust, and C++ implementations.
 pub(crate) fn compute_struct_fingerprint(fields: &[&Field]) -> String {
     let mut field_info_map: HashMap<String, (u32, bool)> = 
HashMap::with_capacity(fields.len());
-    for field in fields {
-        let name = field.ident.as_ref().unwrap().to_string();
+    for (idx, field) in fields.iter().enumerate() {
+        let name = get_field_name(field, idx);
         let type_id = get_type_id_by_type_ast(&field.ty);
         // Match Java's behavior: primitives are non-nullable, everything else 
is nullable
         // In Java: char nullable = rawType.isPrimitive() ? '0' : '1';
diff --git a/rust/fory-derive/src/object/write.rs 
b/rust/fory-derive/src/object/write.rs
index c43a4898c..d7ba72a4a 100644
--- a/rust/fory-derive/src/object/write.rs
+++ b/rust/fory-derive/src/object/write.rs
@@ -17,9 +17,10 @@
 
 use super::util::{
     classify_trait_object_field, compute_struct_version_hash, 
create_wrapper_types_arc,
-    create_wrapper_types_rc, extract_type_name, get_filtered_fields_iter,
-    get_primitive_writer_method, get_struct_name, get_type_id_by_type_ast, 
is_debug_enabled,
-    is_direct_primitive_numeric_type, should_skip_type_info_for_field, 
skip_ref_flag, StructField,
+    create_wrapper_types_rc, extract_type_name, get_field_accessor, 
get_field_name,
+    get_filtered_fields_iter, get_primitive_writer_method, get_struct_name,
+    get_type_id_by_type_ast, is_debug_enabled, 
is_direct_primitive_numeric_type,
+    should_skip_type_info_for_field, skip_ref_flag, StructField,
 };
 use fory_core::types::TypeId;
 use proc_macro2::{Ident, TokenStream};
@@ -114,13 +115,30 @@ pub fn gen_write_type_info() -> TokenStream {
     }
 }
 
+/// Generate write code for a field using index-based access (supports tuple 
structs)
+pub fn gen_write_field_with_index(field: &Field, index: usize, use_self: bool) 
-> TokenStream {
+    let value_ts = get_field_accessor(field, index, use_self);
+    let field_name = get_field_name(field, index);
+    gen_write_field_impl(field, &value_ts, &field_name, use_self)
+}
+
 pub fn gen_write_field(field: &Field, ident: &Ident, use_self: bool) -> 
TokenStream {
-    let ty = &field.ty;
     let value_ts = if use_self {
         quote! { self.#ident }
     } else {
         quote! { #ident }
     };
+    let field_name = ident.to_string();
+    gen_write_field_impl(field, &value_ts, &field_name, use_self)
+}
+
+fn gen_write_field_impl(
+    field: &Field,
+    value_ts: &TokenStream,
+    field_name: &str,
+    use_self: bool,
+) -> TokenStream {
+    let ty = &field.ty;
     let base = match classify_trait_object_field(ty) {
         StructField::BoxDyn => {
             quote! {
@@ -266,20 +284,19 @@ pub fn gen_write_field(field: &Field, ident: &Ident, 
use_self: bool) -> TokenStr
     if is_debug_enabled() && use_self {
         let struct_name = get_struct_name().expect("struct context not set");
         let struct_name_lit = syn::LitStr::new(&struct_name, 
proc_macro2::Span::call_site());
-        let field_name = field.ident.as_ref().unwrap().to_string();
-        let field_name_lit = syn::LitStr::new(&field_name, 
proc_macro2::Span::call_site());
+        let field_name_lit = syn::LitStr::new(field_name, 
proc_macro2::Span::call_site());
         quote! {
             fory_core::serializer::struct_::struct_before_write_field(
                 #struct_name_lit,
                 #field_name_lit,
-                (&self.#ident) as &dyn std::any::Any,
+                (&#value_ts) as &dyn std::any::Any,
                 context,
             );
             #base
             fory_core::serializer::struct_::struct_after_write_field(
                 #struct_name_lit,
                 #field_name_lit,
-                (&self.#ident) as &dyn std::any::Any,
+                (&#value_ts) as &dyn std::any::Any,
                 context,
             );
         }
@@ -290,10 +307,8 @@ pub fn gen_write_field(field: &Field, ident: &Ident, 
use_self: bool) -> TokenStr
 
 pub fn gen_write_data(fields: &[&Field]) -> TokenStream {
     let write_fields_ts: Vec<_> = get_filtered_fields_iter(fields)
-        .map(|field| {
-            let ident = field.ident.as_ref().unwrap();
-            gen_write_field(field, ident, true)
-        })
+        .enumerate()
+        .map(|(idx, field)| gen_write_field_with_index(field, idx, true))
         .collect();
 
     let version_hash = compute_struct_version_hash(fields);
diff --git a/rust/fory-derive/src/util.rs b/rust/fory-derive/src/util.rs
index 5f629e8a1..ffd84d880 100644
--- a/rust/fory-derive/src/util.rs
+++ b/rust/fory-derive/src/util.rs
@@ -23,13 +23,25 @@ pub fn sorted_fields(fields: &Fields) -> Vec<&Field> {
 }
 
 pub fn get_sorted_fields<'a>(fields: &[&'a Field]) -> Vec<&'a Field> {
-    use crate::object::util::get_sorted_field_names;
+    use crate::object::util::{get_sorted_field_names, is_tuple_struct};
 
+    // For tuple structs, we must preserve the original field order
+    // because fields are accessed by index (self.0, self.1, etc.)
+    // Sorting would cause type mismatches during 
serialization/deserialization.
+    if is_tuple_struct(fields) {
+        return fields.to_vec();
+    }
+
+    // For named structs, sort fields by type for optimal memory layout
     let sorted_names = get_sorted_field_names(fields);
     let mut sorted_fields = Vec::with_capacity(fields.len());
 
     for name in &sorted_names {
-        if let Some(field) = fields.iter().find(|f| *f.ident.as_ref().unwrap() 
== name) {
+        // For named structs, field.ident is Some
+        if let Some(field) = fields
+            .iter()
+            .find(|f| f.ident.as_ref().map(|ident| ident == 
name).unwrap_or(false))
+        {
             sorted_fields.push(*field);
         }
     }
diff --git a/rust/tests/tests/test_associated_types.rs 
b/rust/tests/tests/test_associated_types.rs
new file mode 100644
index 000000000..de48a4191
--- /dev/null
+++ b/rust/tests/tests/test_associated_types.rs
@@ -0,0 +1,147 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Tests for structs with associated types (e.g., `C::NodeId`)
+
+use fory_core::fory::Fory;
+use fory_core::{ForyDefault, Serializer};
+use fory_derive::ForyObject;
+
+/// A trait that defines associated types, similar to OpenRaft's RaftTypeConfig
+pub trait TypeConfig: Sized + Send + Sync + 'static {
+    type NodeId: Clone
+        + Default
+        + PartialEq
+        + std::fmt::Debug
+        + Send
+        + Sync
+        + Serializer
+        + ForyDefault
+        + 'static;
+    type Term: Clone
+        + Default
+        + PartialEq
+        + std::fmt::Debug
+        + Send
+        + Sync
+        + Serializer
+        + ForyDefault
+        + 'static;
+}
+
+/// A concrete implementation of TypeConfig
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
+pub struct TestConfig;
+
+impl TypeConfig for TestConfig {
+    type NodeId = u64;
+    type Term = u64;
+}
+
+/// A struct with fields using associated types
+/// The where clause ensures all associated types implement required traits
+#[derive(ForyObject, Debug, Clone, PartialEq)]
+pub struct LeaderId<C>
+where
+    C: TypeConfig,
+    C::NodeId: Serializer + ForyDefault,
+    C::Term: Serializer + ForyDefault,
+{
+    pub term: C::Term,
+    pub node_id: C::NodeId,
+}
+
+#[test]
+fn test_leader_id_with_associated_types() {
+    let mut fory = Fory::default();
+    fory.register::<LeaderId<TestConfig>>(100).unwrap();
+
+    let leader_id: LeaderId<TestConfig> = LeaderId {
+        term: 1,
+        node_id: 42,
+    };
+
+    let bytes = fory.serialize(&leader_id).unwrap();
+    let deserialized: LeaderId<TestConfig> = fory.deserialize(&bytes).unwrap();
+
+    assert_eq!(leader_id, deserialized);
+}
+
+#[test]
+fn test_leader_id_default_values() {
+    let mut fory = Fory::default();
+    fory.register::<LeaderId<TestConfig>>(100).unwrap();
+
+    let leader_id: LeaderId<TestConfig> = LeaderId {
+        term: 0,
+        node_id: 0,
+    };
+
+    let bytes = fory.serialize(&leader_id).unwrap();
+    let deserialized: LeaderId<TestConfig> = fory.deserialize(&bytes).unwrap();
+
+    assert_eq!(leader_id, deserialized);
+}
+
+#[test]
+fn test_vec_of_leader_ids() {
+    let mut fory = Fory::default();
+    fory.register::<LeaderId<TestConfig>>(100).unwrap();
+
+    let leader_ids: Vec<LeaderId<TestConfig>> = vec![
+        LeaderId {
+            term: 1,
+            node_id: 1,
+        },
+        LeaderId {
+            term: 2,
+            node_id: 2,
+        },
+        LeaderId {
+            term: 3,
+            node_id: 3,
+        },
+    ];
+
+    let bytes = fory.serialize(&leader_ids).unwrap();
+    let deserialized: Vec<LeaderId<TestConfig>> = 
fory.deserialize(&bytes).unwrap();
+
+    assert_eq!(leader_ids, deserialized);
+}
+
+#[test]
+fn test_option_leader_id() {
+    let mut fory = Fory::default();
+    fory.register::<LeaderId<TestConfig>>(100).unwrap();
+
+    // Test with Some value
+    let some_leader: Option<LeaderId<TestConfig>> = Some(LeaderId {
+        term: 5,
+        node_id: 10,
+    });
+
+    let bytes = fory.serialize(&some_leader).unwrap();
+    let deserialized: Option<LeaderId<TestConfig>> = 
fory.deserialize(&bytes).unwrap();
+    assert_eq!(some_leader, deserialized);
+
+    // Test with None value
+    let none_leader: Option<LeaderId<TestConfig>> = None;
+
+    let bytes = fory.serialize(&none_leader).unwrap();
+    let deserialized: Option<LeaderId<TestConfig>> = 
fory.deserialize(&bytes).unwrap();
+    assert_eq!(none_leader, deserialized);
+}
diff --git a/rust/tests/tests/test_tuple_struct.rs 
b/rust/tests/tests/test_tuple_struct.rs
new file mode 100644
index 000000000..8c6512130
--- /dev/null
+++ b/rust/tests/tests/test_tuple_struct.rs
@@ -0,0 +1,300 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Tests for tuple struct serialization with #[derive(ForyObject)]
+//!
+//! Tuple structs are structs with unnamed fields, like:
+//! - `struct Point(f64, f64);`
+//! - `struct Wrapper(String);`
+
+use fory_core::fory::Fory;
+use fory_derive::ForyObject;
+use std::collections::HashMap;
+use std::rc::Rc;
+
+// Basic Tuple Structs
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct Point(f64, f64);
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct Wrapper(String);
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct Triple(i32, i64, u32);
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct Single(i32);
+
+#[test]
+fn test_basic_tuple_struct() {
+    let mut fory = Fory::default();
+    fory.register::<Point>(100).unwrap();
+
+    let point = Point(3.15, 2.72);
+    let bytes = fory.serialize(&point).unwrap();
+    let result: Point = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, point);
+}
+
+#[test]
+fn test_single_field_tuple_struct() {
+    let mut fory = Fory::default();
+    fory.register::<Single>(101).unwrap();
+
+    let single = Single(42);
+    let bytes = fory.serialize(&single).unwrap();
+    let result: Single = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, single);
+}
+
+#[test]
+fn test_string_wrapper_tuple_struct() {
+    let mut fory = Fory::default();
+    fory.register::<Wrapper>(102).unwrap();
+
+    let wrapper = Wrapper("hello world".to_string());
+    let bytes = fory.serialize(&wrapper).unwrap();
+    let result: Wrapper = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, wrapper);
+}
+
+#[test]
+fn test_triple_tuple_struct() {
+    let mut fory = Fory::default();
+    fory.register::<Triple>(103).unwrap();
+
+    let triple = Triple(1, 2, 3);
+    let bytes = fory.serialize(&triple).unwrap();
+    let result: Triple = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, triple);
+}
+
+// Tuple Structs with Complex Types
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct WithVec(Vec<i32>, String);
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct WithOption(Option<i32>, Option<String>);
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct WithMap(HashMap<String, i32>);
+
+#[test]
+fn test_tuple_struct_with_vec() {
+    let mut fory = Fory::default();
+    fory.register::<WithVec>(104).unwrap();
+
+    let data = WithVec(vec![1, 2, 3, 4, 5], "test".to_string());
+    let bytes = fory.serialize(&data).unwrap();
+    let result: WithVec = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, data);
+}
+
+#[test]
+fn test_tuple_struct_with_option() {
+    let mut fory = Fory::default();
+    fory.register::<WithOption>(105).unwrap();
+
+    // Test with Some values
+    let data1 = WithOption(Some(42), Some("hello".to_string()));
+    let bytes1 = fory.serialize(&data1).unwrap();
+    let result1: WithOption = fory.deserialize(&bytes1).unwrap();
+    assert_eq!(result1, data1);
+
+    // Test with None values
+    let data2 = WithOption(None, None);
+    let bytes2 = fory.serialize(&data2).unwrap();
+    let result2: WithOption = fory.deserialize(&bytes2).unwrap();
+    assert_eq!(result2, data2);
+
+    // Test with mixed values
+    let data3 = WithOption(Some(100), None);
+    let bytes3 = fory.serialize(&data3).unwrap();
+    let result3: WithOption = fory.deserialize(&bytes3).unwrap();
+    assert_eq!(result3, data3);
+}
+
+#[test]
+fn test_tuple_struct_with_map() {
+    let mut fory = Fory::default();
+    fory.register::<WithMap>(106).unwrap();
+
+    let mut map = HashMap::new();
+    map.insert("one".to_string(), 1);
+    map.insert("two".to_string(), 2);
+    map.insert("three".to_string(), 3);
+
+    let data = WithMap(map);
+    let bytes = fory.serialize(&data).unwrap();
+    let result: WithMap = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, data);
+}
+
+// Nested Tuple Structs
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct Inner(i32, String);
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct Outer(Inner, Vec<Inner>);
+
+#[test]
+fn test_nested_tuple_structs() {
+    let mut fory = Fory::default();
+    fory.register::<Inner>(107).unwrap();
+    fory.register::<Outer>(108).unwrap();
+
+    let inner1 = Inner(1, "first".to_string());
+    let inner2 = Inner(2, "second".to_string());
+    let inner3 = Inner(3, "third".to_string());
+
+    let outer = Outer(inner1.clone(), vec![inner2, inner3]);
+    let bytes = fory.serialize(&outer).unwrap();
+    let result: Outer = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, outer);
+}
+
+// Tuple Struct with Rc (shared reference)
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct WithRc(Rc<String>, Rc<i32>);
+
+#[test]
+fn test_tuple_struct_with_rc() {
+    let mut fory = Fory::default();
+    fory.register::<WithRc>(109).unwrap();
+
+    let data = WithRc(Rc::new("shared".to_string()), Rc::new(42));
+    let bytes = fory.serialize(&data).unwrap();
+    let result: WithRc = fory.deserialize(&bytes).unwrap();
+    assert_eq!(*result.0, "shared");
+    assert_eq!(*result.1, 42);
+}
+
+// Mixed: Tuple Struct inside Named Struct
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct NamedWithTupleStruct {
+    id: i32,
+    point: Point,
+    wrapper: Wrapper,
+}
+
+#[test]
+fn test_named_struct_with_tuple_struct_fields() {
+    let mut fory = Fory::default();
+    fory.register::<Point>(100).unwrap();
+    fory.register::<Wrapper>(102).unwrap();
+    fory.register::<NamedWithTupleStruct>(110).unwrap();
+
+    let data = NamedWithTupleStruct {
+        id: 1,
+        point: Point(1.5, 2.5),
+        wrapper: Wrapper("test".to_string()),
+    };
+
+    let bytes = fory.serialize(&data).unwrap();
+    let result: NamedWithTupleStruct = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, data);
+}
+
+// Tuple Struct with Tuple field
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct TupleStructWithTuple(i32, (String, f64));
+
+#[test]
+fn test_tuple_struct_with_tuple_field() {
+    let mut fory = Fory::default();
+    fory.register::<TupleStructWithTuple>(111).unwrap();
+
+    let data = TupleStructWithTuple(42, ("hello".to_string(), 3.15));
+    let bytes = fory.serialize(&data).unwrap();
+    let result: TupleStructWithTuple = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, data);
+}
+
+// xlang mode tests
+
+#[test]
+fn test_tuple_struct_xlang_mode() {
+    let mut fory = Fory::default().xlang(true);
+    fory.register::<Point>(100).unwrap();
+    fory.register::<Wrapper>(102).unwrap();
+    fory.register::<Triple>(103).unwrap();
+
+    let point = Point(3.15, 2.72);
+    let bytes = fory.serialize(&point).unwrap();
+    let result: Point = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, point);
+
+    let wrapper = Wrapper("xlang test".to_string());
+    let bytes = fory.serialize(&wrapper).unwrap();
+    let result: Wrapper = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, wrapper);
+
+    let triple = Triple(-100, 9999999999i64, 200);
+    let bytes = fory.serialize(&triple).unwrap();
+    let result: Triple = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, triple);
+}
+
+// Edge cases
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct EmptyVecTuple(Vec<i32>);
+
+#[test]
+fn test_tuple_struct_with_empty_vec() {
+    let mut fory = Fory::default();
+    fory.register::<EmptyVecTuple>(112).unwrap();
+
+    let data = EmptyVecTuple(vec![]);
+    let bytes = fory.serialize(&data).unwrap();
+    let result: EmptyVecTuple = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, data);
+}
+
+#[derive(ForyObject, Debug, PartialEq, Clone)]
+struct LargeTupleStruct(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool, 
String);
+
+#[test]
+fn test_large_tuple_struct() {
+    let mut fory = Fory::default();
+    fory.register::<LargeTupleStruct>(113).unwrap();
+
+    let data = LargeTupleStruct(
+        1,
+        2,
+        3,
+        4,
+        5,
+        6,
+        7,
+        8,
+        9.0,
+        10.0,
+        true,
+        "twelve".to_string(),
+    );
+
+    let bytes = fory.serialize(&data).unwrap();
+    let result: LargeTupleStruct = fory.deserialize(&bytes).unwrap();
+    assert_eq!(result, data);
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to