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 40347c84f feat(rust): add generate_default attr, no longer generate
Default by default (#3074)
40347c84f is described below
commit 40347c84ff277535561ea282984c056808b91fae
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 c8f0fd1d4..e0ff796f2 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]