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

chaokunyang pushed a commit to tag v0.14.1-rc1
in repository https://gitbox.apache.org/repos/asf/fory.git

commit 6b104501e4c7580b3ac648511efa7ee98bc73ed7
Author: Damon Zhao <[email protected]>
AuthorDate: Mon Dec 22 18:22:55 2025 +0800

    feat(rust): add generate_default attr, no longer generate Default by 
default (#3074)
    
    …default
    
    
    
    ## Why?
    
    When using `#[derive(ForyObject)]` on a type that already has a manual
    `impl Default`, a compilation error occurs due to conflicting `Default`
    implementations. The previous behavior of auto-generating `Default` was
    problematic for integrating with existing codebases where types have
    custom `Default` implementations.
    
    After discussion with reviewers, we decided to **invert the default
    behavior**: `ForyObject` should NOT generate `impl Default` by default,
    and users who want it can opt-in with `#[fory(generate_default)]`.
    
    ## What does this PR do?
    
    ### Breaking Change
    
    `#[derive(ForyObject)]` **no longer generates `impl Default`** by
    default.
    
    
    Scenario | ForyDefault | Default
    -- | -- | --
    Default (no attribute) | ✅ Generated (standalone impl) | ❌ Not generated
    #[fory(generate_default)] | ✅ Generated | ✅ Generated (delegates
    to ForyDefault)
    User has existing Default | ✅ Generated (delegates to Default) | User's
    own impl
    
    
    
    ### Migration Guide
    
    ```rust
    // Before this PR (v0.14.0)
    #[derive(ForyObject)]
    struct MyStruct { ... }  // Auto-generates Default
    
    // After this PR (v0.15.0) - Option 1: Add derive(Default) explicitly
    #[derive(ForyObject, Default)]
    struct MyStruct { ... }
    
    // After this PR (v0.15.0) - Option 2: Use generate_default attribute
    #[derive(ForyObject)]
    #[fory(generate_default)]
    struct MyStruct { ... }
    ```
    
    ### Changes
    
    1. Added `#[fory(generate_default)]` attribute to opt-in for `Default`
    generation
    2. Changed all internal code generation to use
    `ForyDefault::fory_default()` instead of `Default::default()`
    3. Updated `register_trait_type!` macro to use `ForyDefault` instead of
    `Default`
    4. Updated documentation and tests
    5. Used workspace dependencies for internal crate versions
    
    ## Related issues
    
    None
    
    ## Does this PR introduce any user-facing change?
    
    - [x] Does this PR introduce any public API change?
    - **Breaking**: `#[derive(ForyObject)]` no longer generates `impl
    Default`
    - **New**: `#[fory(generate_default)]` attribute to opt-in for `Default`
    generation
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    This change only affects compile-time code generation. No runtime
    performance impact.
---
 docs/guide/rust/custom-serializers.md           |   8 +-
 rust/Cargo.toml                                 |   6 +-
 rust/README.md                                  |   4 +-
 rust/fory-core/src/serializer/trait_object.rs   |  18 +--
 rust/fory-derive/Cargo.toml                     |   2 +-
 rust/fory-derive/src/lib.rs                     |  36 ++++-
 rust/fory-derive/src/object/derive_enum.rs      |  26 ++--
 rust/fory-derive/src/object/read.rs             |   3 +-
 rust/fory-derive/src/object/serializer.rs       | 113 ++++++++++------
 rust/fory/Cargo.toml                            |   4 +-
 rust/tests/tests/compatible/test_struct.rs      |   6 +-
 rust/tests/tests/compatible/test_struct_enum.rs |   6 +-
 rust/tests/tests/test_cross_language.rs         |   2 +-
 rust/tests/tests/test_generate_default.rs       | 171 ++++++++++++++++++++++++
 rust/tests/tests/test_rc_arc_trait_object.rs    |  11 +-
 rust/tests/tests/test_skip_fields.rs            |  12 +-
 16 files changed, 334 insertions(+), 94 deletions(-)

diff --git a/docs/guide/rust/custom-serializers.md 
b/docs/guide/rust/custom-serializers.md
index 833b20147..a7f403a29 100644
--- a/docs/guide/rust/custom-serializers.md
+++ b/docs/guide/rust/custom-serializers.md
@@ -34,7 +34,7 @@ For types that don't support `#[derive(ForyObject)]`, 
implement the `Serializer`
 use fory::{Fory, ReadContext, WriteContext, Serializer, ForyDefault, Error};
 use std::any::Any;
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Default)]
 struct CustomType {
     value: i32,
     name: String,
@@ -63,6 +63,7 @@ impl Serializer for CustomType {
     }
 }
 
+// ForyDefault delegates to Default
 impl ForyDefault for CustomType {
     fn fory_default() -> Self {
         Self::default()
@@ -70,6 +71,11 @@ impl ForyDefault for CustomType {
 }
 ```
 
+> **Note**: When implementing `ForyDefault` manually, ensure your type also 
implements `Default` if you use `Self::default()`.
+> Alternatively, you can construct a default instance directly in 
`fory_default()`.
+>
+> **Tip**: If your type supports `#[derive(ForyObject)]`, you can use 
`#[fory(generate_default)]` to automatically generate both `ForyDefault` and 
`Default` implementations.
+
 ## Registering Custom Serializers
 
 ```rust
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 5579ab3c6..25b4588c3 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -30,7 +30,7 @@ exclude = [
 resolver = "2"
 
 [workspace.package]
-version = "0.14.0"
+version = "0.15.0"
 rust-version = "1.70"
 description = "Apache Fory: Blazingly fast multi-language serialization 
framework with trait objects and reference support."
 license = "Apache-2.0"
@@ -40,3 +40,7 @@ repository = "https://github.com/apache/fory";
 edition = "2021"
 keywords = ["serialization", "serde", "trait-object", "zero-copy", 
"schema-evolution"]
 categories = ["encoding"]
+
+[workspace.dependencies]
+fory-core = { path = "fory-core", version = "0.15.0" }
+fory-derive = { path = "fory-derive", version = "0.15.0" }
diff --git a/rust/README.md b/rust/README.md
index d84bfcebe..c16651f65 100644
--- a/rust/README.md
+++ b/rust/README.md
@@ -32,7 +32,7 @@ Add Apache Fory™ to your `Cargo.toml`:
 
 ```toml
 [dependencies]
-fory = "0.13"
+fory = "0.14"
 ```
 
 ### Basic Example
@@ -459,7 +459,7 @@ For types that don't support `#[derive(ForyObject)]`, 
implement the `Serializer`
 use fory::{Fory, ReadContext, WriteContext, Serializer, ForyDefault, Error};
 use std::any::Any;
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Default)]
 struct CustomType {
     value: i32,
     name: String,
diff --git a/rust/fory-core/src/serializer/trait_object.rs 
b/rust/fory-core/src/serializer/trait_object.rs
index 7a70e2345..54e639aa3 100644
--- a/rust/fory-core/src/serializer/trait_object.rs
+++ b/rust/fory-core/src/serializer/trait_object.rs
@@ -120,16 +120,10 @@ macro_rules! resolve_and_deserialize {
 macro_rules! register_trait_type {
     ($trait_name:ident, $($impl_type:ty),+ $(,)?) => {
         // 1. Generate Box<dyn Trait> serializer (existing functionality)
-        // Default implementation using first registered type
-        impl std::default::Default for Box<dyn $trait_name> {
-            fn default() -> Self {
-                Box::new(<register_trait_type!(@first_type $($impl_type),+) as 
std::default::Default>::default())
-            }
-        }
-
+        // ForyDefault implementation using first registered type
         impl $crate::serializer::ForyDefault for Box<dyn $trait_name> {
             fn fory_default() -> Self {
-                Box::new(<register_trait_type!(@first_type $($impl_type),+) as 
std::default::Default>::default())
+                Box::new(<register_trait_type!(@first_type $($impl_type),+) as 
$crate::serializer::ForyDefault>::fory_default())
             }
         }
 
@@ -328,15 +322,9 @@ macro_rules! generate_smart_pointer_wrapper {
                 }
             }
 
-            impl std::default::Default for [<$trait_name $ptr_name>] {
-                fn default() -> Self {
-                    
Self($ptr_path::new(<$crate::register_trait_type!(@first_type $($impl_type),+) 
as std::default::Default>::default()))
-                }
-            }
-
             impl $crate::serializer::ForyDefault for [<$trait_name $ptr_name>] 
{
                 fn fory_default() -> Self {
-                    
Self($ptr_path::new(<$crate::register_trait_type!(@first_type $($impl_type),+) 
as std::default::Default>::default()))
+                    
Self($ptr_path::new(<$crate::register_trait_type!(@first_type $($impl_type),+) 
as $crate::serializer::ForyDefault>::fory_default()))
                 }
             }
 
diff --git a/rust/fory-derive/Cargo.toml b/rust/fory-derive/Cargo.toml
index 8e354cc21..1c0c9067e 100644
--- a/rust/fory-derive/Cargo.toml
+++ b/rust/fory-derive/Cargo.toml
@@ -32,7 +32,7 @@ proc-macro = true
 
 [dependencies]
 proc-macro2 = { default-features = false, version = "1.0" }
-fory-core = { path = "../fory-core", version = "0.14.0"}
+fory-core.workspace = true
 syn = { default-features = false, version = "2.0", features = [
     "parsing",
     "proc-macro",
diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs
index 8ecb4cdbc..69a335ba7 100644
--- a/rust/fory-derive/src/lib.rs
+++ b/rust/fory-derive/src/lib.rs
@@ -113,6 +113,10 @@
 //!   `fory_core::serializer::struct_`.
 //! - **`#[fory(skip)]`**: Marks an individual field (or enum variant) to be 
ignored by the
 //!   generated serializer, retaining compatibility with previous releases.
+//! - **`#[fory(generate_default)]`**: Enables the macro to generate `Default` 
implementation.
+//!   By default, `ForyObject` does NOT generate `impl Default` to avoid 
conflicts with existing
+//!   `Default` implementations. Use this attribute when you want the macro to 
generate both
+//!   `ForyDefault` and `Default` for you.
 //!
 //! ## Field Types
 //!
@@ -212,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 = match parse_debug_flag(&input.attrs) {
-        Ok(flag) => flag,
+    let (debug_enabled, generate_default) = match 
parse_fory_attrs(&input.attrs) {
+        Ok(flags) => flags,
         Err(err) => return err.into_compile_error().into(),
     };
 
-    object::derive_serializer(&input, debug_enabled)
+    object::derive_serializer(&input, debug_enabled, generate_default)
 }
 
 /// Derive macro for row-based serialization.
@@ -245,8 +249,10 @@ pub fn proc_macro_derive_fory_row(input: 
proc_macro::TokenStream) -> TokenStream
     derive_row(&input)
 }
 
-fn parse_debug_flag(attrs: &[Attribute]) -> syn::Result<bool> {
+/// Parse fory attributes and return (debug_enabled, generate_default)
+fn parse_fory_attrs(attrs: &[Attribute]) -> syn::Result<(bool, bool)> {
     let mut debug_flag: Option<bool> = None;
+    let mut generate_default_flag: Option<bool> = None;
 
     for attr in attrs {
         if attr.path().is_ident("fory") {
@@ -268,11 +274,31 @@ fn parse_debug_flag(attrs: &[Attribute]) -> 
syn::Result<bool> {
                         Some(_) => debug_flag,
                         None => Some(value),
                     };
+                } else if meta.path.is_ident("generate_default") {
+                    let value = if meta.input.is_empty() {
+                        true
+                    } else {
+                        let lit: LitBool = meta.value()?.parse()?;
+                        lit.value
+                    };
+                    generate_default_flag = match generate_default_flag {
+                        Some(existing) if existing != value => {
+                            return Err(syn::Error::new(
+                                meta.path.span(),
+                                "conflicting `generate_default` attribute 
values",
+                            ));
+                        }
+                        Some(_) => generate_default_flag,
+                        None => Some(value),
+                    };
                 }
                 Ok(())
             })?;
         }
     }
 
-    Ok(debug_flag.unwrap_or(false))
+    Ok((
+        debug_flag.unwrap_or(false),
+        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 efb67a0e0..e32fae431 100644
--- a/rust/fory-derive/src/object/derive_enum.rs
+++ b/rust/fory-derive/src/object/derive_enum.rs
@@ -429,8 +429,9 @@ fn xlang_variant_read_branches(
                     let default_fields: Vec<TokenStream> = fields_unnamed
                         .unnamed
                         .iter()
-                        .map(|_| {
-                            quote! { Default::default() }
+                        .map(|f| {
+                            let ty = &f.ty;
+                            quote! { <#ty as 
fory_core::ForyDefault>::fory_default() }
                         })
                         .collect();
 
@@ -444,7 +445,8 @@ fn xlang_variant_read_branches(
                         .iter()
                         .map(|f| {
                             let field_ident = f.ident.as_ref().unwrap();
-                            quote! { #field_ident: Default::default() }
+                            let ty = &f.ty;
+                            quote! { #field_ident: <#ty as 
fory_core::ForyDefault>::fory_default() }
                         })
                         .collect();
 
@@ -577,7 +579,7 @@ fn rust_compatible_variant_read_branches(
                                     use fory_core::serializer::Serializer;
                                     <#field_ty>::fory_read(context, true, 
true)?
                                 } else {
-                                    Default::default()
+                                    <#field_ty as 
fory_core::ForyDefault>::fory_default()
                                 }
                             }
                         })
@@ -587,7 +589,10 @@ fn rust_compatible_variant_read_branches(
                     let default_fields: Vec<TokenStream> = fields_unnamed
                         .unnamed
                         .iter()
-                        .map(|_| quote! { Default::default() })
+                        .map(|f| {
+                            let ty = &f.ty;
+                            quote! { <#ty as 
fory_core::ForyDefault>::fory_default() }
+                        })
                         .collect();
                     let default_value = quote! { Self::#ident( 
#(#default_fields),* ) };
 
@@ -636,7 +641,8 @@ fn rust_compatible_variant_read_branches(
                         .iter()
                         .map(|f| {
                             let field_ident = f.ident.as_ref().unwrap();
-                            quote! { #field_ident: Default::default() }
+                            let ty = &f.ty;
+                            quote! { #field_ident: <#ty as 
fory_core::ForyDefault>::fory_default() }
                         })
                         .collect();
                     let default_value = quote! { Self::#ident { 
#(#default_fields),* } };
@@ -695,7 +701,10 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
             let default_fields: Vec<TokenStream> = fields_unnamed
                 .unnamed
                 .iter()
-                .map(|_| quote! { Default::default() })
+                .map(|f| {
+                    let ty = &f.ty;
+                    quote! { <#ty as fory_core::ForyDefault>::fory_default() }
+                })
                 .collect();
             quote! { Self::#default_variant_ident( #(#default_fields),* ) }
         }
@@ -705,7 +714,8 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
                 .iter()
                 .map(|f| {
                     let field_ident = f.ident.as_ref().unwrap();
-                    quote! { #field_ident: Default::default() }
+                    let ty = &f.ty;
+                    quote! { #field_ident: <#ty as 
fory_core::ForyDefault>::fory_default() }
                 })
                 .collect();
             quote! { Self::#default_variant_ident { #(#default_fields),* } }
diff --git a/rust/fory-derive/src/object/read.rs 
b/rust/fory-derive/src/object/read.rs
index add028568..e53a32789 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -88,8 +88,9 @@ pub(crate) fn assign_value(fields: &[&Field]) -> 
Vec<TokenStream> {
                 }
                 _ => {
                     if need_declared_by_option(field) {
+                        let ty = &field.ty;
                         quote! {
-                            #name: #var_name.unwrap_or_default()
+                            #name: #var_name.unwrap_or_else(|| <#ty as 
fory_core::ForyDefault>::fory_default())
                         }
                     } else {
                         quote! {
diff --git a/rust/fory-derive/src/object/serializer.rs 
b/rust/fory-derive/src/object/serializer.rs
index ee5fb7201..374745392 100644
--- a/rust/fory-derive/src/object/serializer.rs
+++ b/rust/fory-derive/src/object/serializer.rs
@@ -36,7 +36,11 @@ fn has_existing_default(ast: &syn::DeriveInput, trait_name: 
&str) -> bool {
     })
 }
 
-pub fn derive_serializer(ast: &syn::DeriveInput, debug_enabled: bool) -> 
TokenStream {
+pub fn derive_serializer(
+    ast: &syn::DeriveInput,
+    debug_enabled: bool,
+    generate_default: bool,
+) -> TokenStream {
     let name = &ast.ident;
     use crate::object::util::{clear_struct_context, set_struct_context};
     set_struct_context(&name.to_string(), debug_enabled);
@@ -44,7 +48,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput, 
debug_enabled: bool) -> TokenSt
     // 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_impl(ast, generate_default)
     } else {
         quote! {}
     };
@@ -242,9 +246,14 @@ pub fn derive_serializer(ast: &syn::DeriveInput, 
debug_enabled: bool) -> TokenSt
     code
 }
 
-fn generate_default_impl(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
+fn generate_default_impl(
+    ast: &syn::DeriveInput,
+    generate_default: bool,
+) -> proc_macro2::TokenStream {
     let name = &ast.ident;
-    let has_existing_default = has_existing_default(ast, "Default");
+    // 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");
 
     match &ast.data {
         Data::Struct(s) => {
@@ -295,15 +304,8 @@ fn generate_default_impl(ast: &syn::DeriveInput) -> 
proc_macro2::TokenStream {
                 }
             });
 
-            if has_existing_default {
-                quote! {
-                   impl fory_core::ForyDefault for #name {
-                        fn fory_default() -> Self {
-                            Self::default()
-                        }
-                    }
-                }
-            } else {
+            if should_generate_default {
+                // User requested Default generation via 
#[fory(generate_default)]
                 quote! {
                     impl fory_core::ForyDefault for #name {
                         fn fory_default() -> Self {
@@ -318,6 +320,18 @@ fn generate_default_impl(ast: &syn::DeriveInput) -> 
proc_macro2::TokenStream {
                         }
                     }
                 }
+            } else {
+                // Default case: only generate ForyDefault, not Default
+                // This avoids conflicts with existing Default implementations
+                quote! {
+                   impl fory_core::ForyDefault for #name {
+                        fn fory_default() -> Self {
+                            Self {
+                                #(#field_inits),*
+                            }
+                        }
+                    }
+                }
             }
         }
         Data::Enum(e) => {
@@ -327,25 +341,42 @@ fn generate_default_impl(ast: &syn::DeriveInput) -> 
proc_macro2::TokenStream {
                 .iter()
                 .any(|v| v.attrs.iter().any(|attr| 
attr.path().is_ident("default")));
 
-            // For C-like enums, implement Default by returning the first 
variant
-            // Only if there's no #[default] attribute (which means Default is 
being derived)
-            if !has_default_variant {
-                if let Some(first_variant) = e.variants.first() {
-                    let variant_ident = &first_variant.ident;
-                    let field_defaults = match &first_variant.fields {
-                        syn::Fields::Unit => quote! {},
-                        syn::Fields::Unnamed(fields) => {
-                            let defaults =
-                                (0..fields.unnamed.len()).map(|_| quote! { 
Default::default() });
-                            quote! { (#(#defaults),*) }
-                        }
-                        syn::Fields::Named(fields) => {
-                            let field_idents = fields.named.iter().map(|f| 
&f.ident);
-                            let defaults =
-                                fields.named.iter().map(|_| quote! { 
Default::default() });
-                            quote! { { #(#field_idents: #defaults),* } }
+            // Check if user has #[derive(Default)] on the enum
+            let has_derived_default = has_existing_default(ast, "Default");
+
+            if let Some(first_variant) = e.variants.first() {
+                let variant_ident = &first_variant.ident;
+                let field_defaults = match &first_variant.fields {
+                    syn::Fields::Unit => quote! {},
+                    syn::Fields::Unnamed(fields) => {
+                        let defaults = fields.unnamed.iter().map(|f| {
+                            let ty = &f.ty;
+                            quote! { <#ty as 
fory_core::ForyDefault>::fory_default() }
+                        });
+                        quote! { (#(#defaults),*) }
+                    }
+                    syn::Fields::Named(fields) => {
+                        let field_inits = fields.named.iter().map(|f| {
+                            let ident = &f.ident;
+                            let ty = &f.ty;
+                            quote! { #ident: <#ty as 
fory_core::ForyDefault>::fory_default() }
+                        });
+                        quote! { { #(#field_inits),* } }
+                    }
+                };
+
+                if has_derived_default || has_default_variant {
+                    // User has #[derive(Default)] or #[default] attribute
+                    // Only generate ForyDefault that delegates to Default
+                    quote! {
+                        impl fory_core::ForyDefault for #name {
+                            fn fory_default() -> Self {
+                                Self::default()
+                            }
                         }
-                    };
+                    }
+                } else if should_generate_default {
+                    // User requested Default generation via 
#[fory(generate_default)]
                     quote! {
                         impl fory_core::ForyDefault for #name {
                             fn fory_default() -> Self {
@@ -360,21 +391,17 @@ fn generate_default_impl(ast: &syn::DeriveInput) -> 
proc_macro2::TokenStream {
                         }
                     }
                 } else {
-                    // impl fory_core::ForyDefault for #name {
-                    //     fn fory_default() -> Self {
-                    //         panic!("No unit-like variants found in enum 
{}", stringify!(#name));
-                    //     }
-                    // }
-                    quote! {}
-                }
-            } else {
-                quote! {
-                    impl fory_core::ForyDefault for #name {
-                        fn fory_default() -> Self {
-                            Self::default()
+                    // Default case: only generate ForyDefault, not Default
+                    quote! {
+                        impl fory_core::ForyDefault for #name {
+                            fn fory_default() -> Self {
+                                Self::#variant_ident #field_defaults
+                            }
                         }
                     }
                 }
+            } else {
+                quote! {}
             }
         }
         Data::Union(_) => quote! {},
diff --git a/rust/fory/Cargo.toml b/rust/fory/Cargo.toml
index a7e3d7290..a8c2645b9 100644
--- a/rust/fory/Cargo.toml
+++ b/rust/fory/Cargo.toml
@@ -29,5 +29,5 @@ categories.workspace = true
 description = "Apache Fory: Blazingly fast multi-language serialization 
framework with trait objects and reference support."
 
 [dependencies]
-fory-core = { path = "../fory-core", version = "0.14.0"}
-fory-derive = { path = "../fory-derive", version = "0.14.0"}
+fory-core.workspace = true
+fory-derive.workspace = true
diff --git a/rust/tests/tests/compatible/test_struct.rs 
b/rust/tests/tests/compatible/test_struct.rs
index 44c32726b..cc5c1655b 100644
--- a/rust/tests/tests/compatible/test_struct.rs
+++ b/rust/tests/tests/compatible/test_struct.rs
@@ -136,7 +136,8 @@ fn nonexistent_struct() {
     };
     let bin = fory1.serialize(&person).unwrap();
     let obj: Person2 = fory2.deserialize(&bin).unwrap();
-    assert_eq!(obj.f2, Item2::default());
+    use fory_core::ForyDefault;
+    assert_eq!(obj.f2, Item2::fory_default());
     assert_eq!(obj.f3, i64::default());
     assert_eq!(obj.last, person.last);
 }
@@ -376,7 +377,8 @@ fn nullable_struct() {
     let person2: Person2 = fory2.deserialize(&bin).unwrap();
 
     assert_eq!(person2.f1.unwrap(), person1.f1);
-    assert_eq!(person2.f2, Item::default());
+    use fory_core::ForyDefault;
+    assert_eq!(person2.f2, Item::fory_default());
     assert_eq!(person2.f3, person1.f3.unwrap());
     assert_eq!(person2.last, person1.last);
 }
diff --git a/rust/tests/tests/compatible/test_struct_enum.rs 
b/rust/tests/tests/compatible/test_struct_enum.rs
index 052c7439f..0952b6833 100644
--- a/rust/tests/tests/compatible/test_struct_enum.rs
+++ b/rust/tests/tests/compatible/test_struct_enum.rs
@@ -180,7 +180,8 @@ fn nonexistent_struct() {
     };
     let bin = fory1.serialize(&person).unwrap();
     let obj: Person2 = fory2.deserialize(&bin).unwrap();
-    assert_eq!(obj.f2, Item2::default());
+    use fory_core::ForyDefault;
+    assert_eq!(obj.f2, Item2::fory_default());
     assert_eq!(obj.f3, i64::default());
     assert_eq!(obj.last, person.last);
 }
@@ -420,7 +421,8 @@ fn nullable_struct() {
     let person2: Person2 = fory2.deserialize(&bin).unwrap();
 
     assert_eq!(person2.f1.unwrap(), person1.f1);
-    assert_eq!(person2.f2, Item::default());
+    use fory_core::ForyDefault;
+    assert_eq!(person2.f2, Item::fory_default());
     assert_eq!(person2.f3, person1.f3.unwrap());
     assert_eq!(person2.last, person1.last);
 }
diff --git a/rust/tests/tests/test_cross_language.rs 
b/rust/tests/tests/test_cross_language.rs
index 2afc3f470..e415534e8 100644
--- a/rust/tests/tests/test_cross_language.rs
+++ b/rust/tests/tests/test_cross_language.rs
@@ -32,7 +32,7 @@ fn get_data_file() -> String {
     std::env::var("DATA_FILE").expect("DATA_FILE not set")
 }
 
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyObject, Debug, PartialEq, Default)]
 struct Empty {}
 
 #[derive(ForyObject, Debug, PartialEq, Default)]
diff --git a/rust/tests/tests/test_generate_default.rs 
b/rust/tests/tests/test_generate_default.rs
new file mode 100644
index 000000000..b96d9af30
--- /dev/null
+++ b/rust/tests/tests/test_generate_default.rs
@@ -0,0 +1,171 @@
+// 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 the `#[fory(generate_default)]` attribute.
+//!
+//! By default, `ForyObject` does NOT generate `impl Default` to avoid 
conflicts
+//! with existing `Default` implementations. Use `#[fory(generate_default)]` 
when
+//! you want the macro to generate both `ForyDefault` and `Default` for you.
+
+use fory_core::fory::Fory;
+use fory_derive::ForyObject;
+
+/// Test struct with manual Default implementation.
+/// ForyObject does NOT generate Default by default, so there's no conflict.
+#[derive(ForyObject, Debug, PartialEq)]
+struct NodeWithCustomDefault {
+    addr: String,
+}
+
+impl Default for NodeWithCustomDefault {
+    fn default() -> Self {
+        Self {
+            addr: "localhost:8080".to_string(),
+        }
+    }
+}
+
+#[test]
+fn test_no_default_conflict() {
+    let mut fory = Fory::default();
+    fory.register::<NodeWithCustomDefault>(1).unwrap();
+
+    let node = NodeWithCustomDefault {
+        addr: "192.168.1.1:3000".to_string(),
+    };
+
+    let bytes = fory.serialize(&node).unwrap();
+    let decoded: NodeWithCustomDefault = fory.deserialize(&bytes).unwrap();
+    assert_eq!(node, decoded);
+}
+
+#[test]
+fn test_custom_default_is_preserved() {
+    // Verify that our custom Default implementation is used
+    let node = NodeWithCustomDefault::default();
+    assert_eq!(node.addr, "localhost:8080");
+}
+
+/// Test enum with manual Default implementation.
+/// ForyObject does NOT generate Default by default.
+#[derive(ForyObject, Debug, PartialEq)]
+enum StatusWithCustomDefault {
+    Active,
+    Inactive,
+    Pending,
+}
+
+// Allow derivable_impls because we're specifically testing manual Default impl
+#[allow(clippy::derivable_impls)]
+impl Default for StatusWithCustomDefault {
+    fn default() -> Self {
+        // Custom default: Pending instead of Active (first variant)
+        StatusWithCustomDefault::Pending
+    }
+}
+
+#[test]
+fn test_enum_no_default_conflict() {
+    let mut fory = Fory::default();
+    fory.register::<StatusWithCustomDefault>(2).unwrap();
+
+    let status = StatusWithCustomDefault::Active;
+    let bytes = fory.serialize(&status).unwrap();
+    let decoded: StatusWithCustomDefault = fory.deserialize(&bytes).unwrap();
+    assert_eq!(status, decoded);
+}
+
+#[test]
+fn test_enum_custom_default_is_preserved() {
+    // Verify that our custom Default implementation is used
+    let status = StatusWithCustomDefault::default();
+    assert_eq!(status, StatusWithCustomDefault::Pending);
+}
+
+/// Test that generate_default = true generates Default impl
+#[derive(ForyObject, Debug, PartialEq)]
+#[fory(generate_default)]
+struct StructWithGeneratedDefault {
+    value: i32,
+}
+
+#[test]
+fn test_generate_default_struct() {
+    let mut fory = Fory::default();
+    fory.register::<StructWithGeneratedDefault>(3).unwrap();
+
+    let data = StructWithGeneratedDefault { value: 42 };
+    let bytes = fory.serialize(&data).unwrap();
+    let decoded: StructWithGeneratedDefault = 
fory.deserialize(&bytes).unwrap();
+    assert_eq!(data, decoded);
+
+    // Default should work (generated by ForyObject)
+    let default_data = StructWithGeneratedDefault::default();
+    assert_eq!(default_data.value, 0);
+}
+
+/// Test that generate_default = true generates Default impl for enums
+#[derive(ForyObject, Debug, PartialEq)]
+#[fory(generate_default)]
+enum EnumWithGeneratedDefault {
+    First,
+    Second,
+    Third,
+}
+
+#[test]
+fn test_generate_default_enum() {
+    let mut fory = Fory::default();
+    fory.register::<EnumWithGeneratedDefault>(4).unwrap();
+
+    let data = EnumWithGeneratedDefault::Second;
+    let bytes = fory.serialize(&data).unwrap();
+    let decoded: EnumWithGeneratedDefault = fory.deserialize(&bytes).unwrap();
+    assert_eq!(data, decoded);
+
+    // Default should be First variant (generated by ForyObject)
+    let default_data = EnumWithGeneratedDefault::default();
+    assert_eq!(default_data, EnumWithGeneratedDefault::First);
+}
+
+/// Test that generate_default = false works (same as not specifying)
+#[derive(ForyObject, Debug, PartialEq)]
+#[fory(generate_default = false)]
+struct StructWithoutGeneratedDefault {
+    value: i32,
+}
+
+impl Default for StructWithoutGeneratedDefault {
+    fn default() -> Self {
+        Self { value: 100 }
+    }
+}
+
+#[test]
+fn test_generate_default_false() {
+    let mut fory = Fory::default();
+    fory.register::<StructWithoutGeneratedDefault>(5).unwrap();
+
+    let data = StructWithoutGeneratedDefault { value: 42 };
+    let bytes = fory.serialize(&data).unwrap();
+    let decoded: StructWithoutGeneratedDefault = 
fory.deserialize(&bytes).unwrap();
+    assert_eq!(data, decoded);
+
+    // Custom Default should be used
+    let default_data = StructWithoutGeneratedDefault::default();
+    assert_eq!(default_data.value, 100);
+}
diff --git a/rust/tests/tests/test_rc_arc_trait_object.rs 
b/rust/tests/tests/test_rc_arc_trait_object.rs
index 68ccaa872..2bbed8a1a 100644
--- a/rust/tests/tests/test_rc_arc_trait_object.rs
+++ b/rust/tests/tests/test_rc_arc_trait_object.rs
@@ -160,13 +160,14 @@ fn test_wrapper_polymorphism() {
 
 #[test]
 fn test_wrapper_default_implementations() {
-    // Test that wrapper types have proper Default implementations
-    let default_rc = AnimalRc::default();
-    // Dog::default() should have empty name
+    use fory_core::ForyDefault;
+    // Test that wrapper types have proper ForyDefault implementations
+    let default_rc = AnimalRc::fory_default();
+    // Dog::fory_default() should have empty name
     assert_eq!(default_rc.as_ref().name(), "");
 
-    let default_arc = AnimalArc::default();
-    // Dog::default() should have empty name
+    let default_arc = AnimalArc::fory_default();
+    // Dog::fory_default() should have empty name
     assert_eq!(default_arc.as_ref().name(), "");
 }
 
diff --git a/rust/tests/tests/test_skip_fields.rs 
b/rust/tests/tests/test_skip_fields.rs
index 9a9c43059..3f9cabd3e 100644
--- a/rust/tests/tests/test_skip_fields.rs
+++ b/rust/tests/tests/test_skip_fields.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use fory_core::{Fory, ForyDefault, Reader};
+use fory_core::{Fory, Reader};
 use fory_derive::ForyObject;
 
 #[derive(ForyObject, Debug, PartialEq)]
@@ -123,7 +123,8 @@ fn test_nested_skip_functionality() {
 
     assert_eq!(original.normal_field, decoded.normal_field);
     assert_eq!(original.nested, decoded.nested);
-    assert_eq!(decoded.skipped_nested, NestedStruct::default());
+    use fory_core::ForyDefault;
+    assert_eq!(decoded.skipped_nested, NestedStruct::fory_default());
 }
 
 #[test]
@@ -197,9 +198,10 @@ fn test_complex_nested_skip() {
         original.nested.serialized_field,
         decoded.nested.serialized_field
     );
+    use fory_core::ForyDefault;
     assert_eq!(decoded.nested.skipped_field, String::default());
     assert_eq!(decoded.skipped_field, String::default());
-    assert_eq!(decoded.skipped_nested, TestSkipFields::default());
+    assert_eq!(decoded.skipped_nested, TestSkipFields::fory_default());
 }
 
 #[test]
@@ -216,7 +218,8 @@ fn test_enum_skip() {
     let original_skip = TestEnumSkip::Deleted;
     let bytes = fory.serialize(&original_skip).unwrap();
     let decoded: TestEnumSkip = fory.deserialize(&bytes).unwrap();
-    assert_eq!(decoded, TestEnumSkip::default());
+    use fory_core::ForyDefault;
+    assert_eq!(decoded, TestEnumSkip::fory_default());
 }
 
 #[test]
@@ -298,7 +301,6 @@ fn test_skip_with_different_types() {
 
 #[test]
 fn test_trait_object_serialization() {
-    use fory_core::ForyDefault;
     use fory_core::Serializer;
     use fory_core::{register_trait_type, Fory};
     use fory_derive::ForyObject;


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


Reply via email to