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 62a386d35 feat(rust): add array support for rust (#2874)
62a386d35 is described below
commit 62a386d35b8edca7df63749807f7c8938c964124
Author: Shawn Yang <[email protected]>
AuthorDate: Mon Nov 3 00:03:47 2025 +0800
feat(rust): add array support for rust (#2874)
## Why?
<!-- Describe the purpose of this PR. -->
## What does this PR do?
add array support for rust, use same collection protocol
## Related issues
<!--
Is there any related issue? If this PR closes them you say say
fix/closes:
- #xxxx0
- #xxxx1
- Fixes #xxxx2
-->
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fory/issues/new/choose) describing the
need to do so and update the document if necessary.
Delete section if not applicable.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
Delete section if not applicable.
-->
---
rust/fory-core/src/serializer/array.rs | 312 +++++++++++++++++++++++++++++++
rust/fory-core/src/serializer/list.rs | 4 +-
rust/fory-core/src/serializer/mod.rs | 1 +
rust/fory-derive/src/object/util.rs | 85 ++++++++-
rust/tests/tests/test_array.rs | 324 +++++++++++++++++++++++++++++++++
5 files changed, 723 insertions(+), 3 deletions(-)
diff --git a/rust/fory-core/src/serializer/array.rs
b/rust/fory-core/src/serializer/array.rs
new file mode 100644
index 000000000..fc9537115
--- /dev/null
+++ b/rust/fory-core/src/serializer/array.rs
@@ -0,0 +1,312 @@
+// 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.
+
+use crate::error::Error;
+use crate::resolver::context::ReadContext;
+use crate::resolver::context::WriteContext;
+use crate::resolver::type_resolver::TypeResolver;
+use crate::serializer::primitive_list;
+use crate::serializer::{ForyDefault, Serializer};
+use crate::types::TypeId;
+use std::mem;
+use std::mem::MaybeUninit;
+
+use super::collection::{
+ read_collection_type_info, write_collection_data,
write_collection_type_info,
+ DECL_ELEMENT_TYPE, HAS_NULL, IS_SAME_TYPE,
+};
+use super::list::{get_primitive_type_id, is_primitive_type};
+use crate::ensure;
+use crate::types::RefFlag;
+
+// Collection header flags (matching collection.rs private constants)
+const TRACKING_REF: u8 = 0b1;
+
+/// Validates that the deserialized length matches the expected array size N.
+#[inline(always)]
+fn validate_array_length(actual: usize, expected: usize) -> Result<(), Error> {
+ if actual != expected {
+ return Err(Error::invalid_data(format!(
+ "Array length mismatch: expected {}, got {}",
+ expected, actual
+ )));
+ }
+ Ok(())
+}
+
+/// Converts initialized MaybeUninit array to a regular array.
+/// # Safety
+/// All elements in the array must be initialized.
+#[inline(always)]
+unsafe fn assume_array_init<T, const N: usize>(arr:
&[std::mem::MaybeUninit<T>; N]) -> [T; N] {
+ std::ptr::read(arr as *const _ as *const [T; N])
+}
+
+/// Read primitive array directly without intermediate Vec allocation
+#[inline]
+fn read_primitive_array<T, const N: usize>(context: &mut ReadContext) ->
Result<[T; N], Error>
+where
+ T: Serializer + ForyDefault,
+{
+ // Read the size in bytes
+ let size_bytes = context.reader.read_varuint32()? as usize;
+ let elem_size = mem::size_of::<T>();
+ if size_bytes % elem_size != 0 {
+ return Err(Error::invalid_data("Invalid data length"));
+ }
+ let len = size_bytes / elem_size;
+ validate_array_length(len, N)?;
+ // Handle zero-sized arrays
+ if N == 0 {
+ // Safe: std::mem::zeroed() is explicitly safe for zero-sized types
+ return Ok(unsafe { std::mem::zeroed() });
+ }
+ // Create uninitialized array
+ let mut arr: [MaybeUninit<T>; N] = unsafe {
MaybeUninit::uninit().assume_init() };
+ // Read bytes directly into array memory
+ unsafe {
+ let dst_ptr = arr.as_mut_ptr() as *mut u8;
+ let src = context.reader.read_bytes(size_bytes)?;
+ std::ptr::copy_nonoverlapping(src.as_ptr(), dst_ptr, size_bytes);
+ }
+ // Safety: all elements are now initialized with data from the reader
+ Ok(unsafe { assume_array_init(&arr) })
+}
+
+/// Read complex (non-primitive) array directly without intermediate Vec
allocation
+#[inline]
+fn read_complex_array<T, const N: usize>(context: &mut ReadContext) ->
Result<[T; N], Error>
+where
+ T: Serializer + ForyDefault,
+{
+ // Read collection length
+ let len = context.reader.read_varuint32()? as usize;
+ validate_array_length(len, N)?;
+ // Handle zero-sized arrays
+ if N == 0 {
+ // Safe: std::mem::zeroed() is explicitly safe for zero-sized types
+ return Ok(unsafe { std::mem::zeroed() });
+ }
+ // Handle polymorphic or shared ref types - need to use collection logic
+ if T::fory_is_polymorphic() || T::fory_is_shared_ref() {
+ return read_complex_array_dyn_ref(context, len);
+ }
+ // Read header
+ let header = context.reader.read_u8()?;
+ let declared = (header & DECL_ELEMENT_TYPE) != 0;
+ if !declared {
+ T::fory_read_type_info(context)?;
+ }
+ let has_null = (header & HAS_NULL) != 0;
+ ensure!(
+ (header & IS_SAME_TYPE) != 0,
+ Error::type_error("Type inconsistent, target type is not polymorphic")
+ );
+ // Create uninitialized array
+ let mut arr: [MaybeUninit<T>; N] = unsafe {
MaybeUninit::uninit().assume_init() };
+ // Read elements directly into array
+ if !has_null {
+ for elem_slot in &mut arr[..] {
+ let elem = T::fory_read_data(context)?;
+ elem_slot.write(elem);
+ }
+ } else {
+ for elem_slot in &mut arr[..] {
+ let flag = context.reader.read_i8()?;
+ let elem = if flag == RefFlag::Null as i8 {
+ T::fory_default()
+ } else {
+ T::fory_read_data(context)?
+ };
+ elem_slot.write(elem);
+ }
+ }
+ // Safety: all elements are now initialized
+ Ok(unsafe { std::ptr::read(&arr as *const _ as *const [T; N]) })
+}
+
+/// Read complex array with dynamic/polymorphic types
+#[inline]
+fn read_complex_array_dyn_ref<T, const N: usize>(
+ context: &mut ReadContext,
+ len: usize,
+) -> Result<[T; N], Error>
+where
+ T: Serializer + ForyDefault,
+{
+ // Read header
+ let header = context.reader.read_u8()?;
+ let is_track_ref = (header & TRACKING_REF) != 0;
+ let is_same_type = (header & IS_SAME_TYPE) != 0;
+ let has_null = (header & HAS_NULL) != 0;
+ let is_declared = (header & DECL_ELEMENT_TYPE) != 0;
+ // Create uninitialized array
+ let mut arr: [MaybeUninit<T>; N] = unsafe {
MaybeUninit::uninit().assume_init() };
+ // Read elements
+ if is_same_type {
+ let type_info = if !is_declared {
+ context.read_any_typeinfo()?
+ } else {
+ let rs_type_id = std::any::TypeId::of::<T>();
+ context.get_type_resolver().get_type_info(&rs_type_id)?
+ };
+ if is_track_ref {
+ for elem_slot in arr.iter_mut().take(len) {
+ let elem = T::fory_read_with_type_info(context, true,
type_info.clone())?;
+ elem_slot.write(elem);
+ }
+ } else if !has_null {
+ for elem_slot in arr.iter_mut().take(len) {
+ let elem = T::fory_read_with_type_info(context, false,
type_info.clone())?;
+ elem_slot.write(elem);
+ }
+ } else {
+ for elem_slot in arr.iter_mut().take(len) {
+ let flag = context.reader.read_i8()?;
+ let elem = if flag == RefFlag::Null as i8 {
+ T::fory_default()
+ } else {
+ T::fory_read_with_type_info(context, false,
type_info.clone())?
+ };
+ elem_slot.write(elem);
+ }
+ }
+ } else {
+ for elem_slot in arr.iter_mut().take(len) {
+ let elem = T::fory_read(context, is_track_ref, true)?;
+ elem_slot.write(elem);
+ }
+ }
+ // Safety: all elements are now initialized
+ Ok(unsafe { std::ptr::read(&arr as *const _ as *const [T; N]) })
+}
+
+// Implement Serializer for fixed-size arrays [T; N] where N is a const
generic parameter
+impl<T: Serializer + ForyDefault, const N: usize> Serializer for [T; N] {
+ #[inline(always)]
+ fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error>
{
+ if is_primitive_type::<T>() {
+ primitive_list::fory_write_data(self.as_slice(), context)
+ } else {
+ write_collection_data(self.iter(), context, false)
+ }
+ }
+
+ #[inline(always)]
+ fn fory_write_data_generic(
+ &self,
+ context: &mut WriteContext,
+ has_generics: bool,
+ ) -> Result<(), Error> {
+ if is_primitive_type::<T>() {
+ primitive_list::fory_write_data(self.as_slice(), context)
+ } else {
+ write_collection_data(self.iter(), context, has_generics)
+ }
+ }
+
+ #[inline(always)]
+ fn fory_write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ let id = get_primitive_type_id::<T>();
+ if id != TypeId::UNKNOWN {
+ primitive_list::fory_write_type_info(context, id)
+ } else {
+ write_collection_type_info(context, TypeId::LIST as u32)
+ }
+ }
+
+ #[inline(always)]
+ fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
+ if is_primitive_type::<T>() {
+ // Read primitive array data directly without intermediate Vec
allocation
+ read_primitive_array(context)
+ } else {
+ // Read collection data directly into array without intermediate
Vec
+ read_complex_array(context)
+ }
+ }
+
+ #[inline(always)]
+ fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ let id = get_primitive_type_id::<T>();
+ if id != TypeId::UNKNOWN {
+ primitive_list::fory_read_type_info(context, id)
+ } else {
+ read_collection_type_info(context, TypeId::LIST as u32)
+ }
+ }
+
+ #[inline(always)]
+ fn fory_reserved_space() -> usize {
+ if is_primitive_type::<T>() {
+ primitive_list::fory_reserved_space::<T>()
+ } else {
+ // size of the array length
+ mem::size_of::<u32>()
+ }
+ }
+
+ #[inline(always)]
+ fn fory_static_type_id() -> TypeId
+ where
+ Self: Sized,
+ {
+ let id = get_primitive_type_id::<T>();
+ if id != TypeId::UNKNOWN {
+ id
+ } else {
+ TypeId::LIST
+ }
+ }
+
+ #[inline(always)]
+ fn fory_get_type_id(_: &TypeResolver) -> Result<u32, Error> {
+ Ok(Self::fory_static_type_id() as u32)
+ }
+
+ #[inline(always)]
+ fn fory_type_id_dyn(&self, _: &TypeResolver) -> Result<u32, Error> {
+ Ok(Self::fory_static_type_id() as u32)
+ }
+
+ #[inline(always)]
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+}
+
+impl<T, const N: usize> ForyDefault for [T; N]
+where
+ T: ForyDefault,
+{
+ #[inline(always)]
+ fn fory_default() -> Self {
+ // Create an array by calling fory_default() for each element
+ // We use MaybeUninit for safe initialization
+
+ let mut arr: [MaybeUninit<T>; N] = unsafe {
MaybeUninit::uninit().assume_init() };
+ for elem in &mut arr {
+ elem.write(T::fory_default());
+ }
+
+ // Safety: all elements are initialized
+ unsafe {
+ // Transmute from [MaybeUninit<T>; N] to [T; N]
+ std::ptr::read(&arr as *const _ as *const [T; N])
+ }
+ }
+}
diff --git a/rust/fory-core/src/serializer/list.rs
b/rust/fory-core/src/serializer/list.rs
index 4db945d76..b1ab31c6f 100644
--- a/rust/fory-core/src/serializer/list.rs
+++ b/rust/fory-core/src/serializer/list.rs
@@ -31,7 +31,7 @@ use super::collection::{
};
#[inline(always)]
-fn get_primitive_type_id<T: Serializer>() -> TypeId {
+pub(super) fn get_primitive_type_id<T: Serializer>() -> TypeId {
if T::fory_is_wrapper_type() {
return TypeId::UNKNOWN;
}
@@ -52,7 +52,7 @@ fn get_primitive_type_id<T: Serializer>() -> TypeId {
}
#[inline(always)]
-pub fn is_primitive_type<T: Serializer>() -> bool {
+pub(super) fn is_primitive_type<T: Serializer>() -> bool {
if T::fory_is_wrapper_type() {
return false;
}
diff --git a/rust/fory-core/src/serializer/mod.rs
b/rust/fory-core/src/serializer/mod.rs
index e98b7dfad..66686b331 100644
--- a/rust/fory-core/src/serializer/mod.rs
+++ b/rust/fory-core/src/serializer/mod.rs
@@ -17,6 +17,7 @@
pub mod any;
mod arc;
+mod array;
mod bool;
mod box_;
pub mod collection;
diff --git a/rust/fory-derive/src/object/util.rs
b/rust/fory-derive/src/object/util.rs
index cee4f3370..578d38d0c 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -314,6 +314,8 @@ pub(super) fn extract_type_name(ty: &Type) -> String {
"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()
}
@@ -348,6 +350,15 @@ pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
};
}
+ // Handle arrays - extract element type
+ if let Type::Array(array) = ty {
+ let elem_node = parse_generic_tree(&array.elem);
+ return TypeNode {
+ name: "Array".to_string(),
+ generics: vec![elem_node],
+ };
+ }
+
let name = extract_type_name(ty);
let generics = if let Type::Path(type_path) = ty {
@@ -389,6 +400,52 @@ pub(super) fn generic_tree_to_tokens(node: &TypeNode) ->
TokenStream {
};
}
+ // Special handling for arrays: treat them as lists with element type
generic
+ if node.name == "Array" {
+ if let Some(elem_node) = node.generics.first() {
+ let elem_token = generic_tree_to_tokens(elem_node);
+ // Check if element is primitive to determine the correct type ID
+ let is_primitive_elem =
PRIMITIVE_TYPE_NAMES.contains(&elem_node.name.as_str());
+ if is_primitive_elem {
+ // For primitive arrays, use primitive array type ID
+ let type_id_token = match elem_node.name.as_str() {
+ "bool" => quote! { fory_core::types::TypeId::BOOL_ARRAY as
u32 },
+ "i8" => quote! { fory_core::types::TypeId::INT8_ARRAY as
u32 },
+ "i16" => quote! { fory_core::types::TypeId::INT16_ARRAY as
u32 },
+ "i32" => quote! { fory_core::types::TypeId::INT32_ARRAY as
u32 },
+ "i64" => quote! { fory_core::types::TypeId::INT64_ARRAY as
u32 },
+ "f32" => quote! { fory_core::types::TypeId::FLOAT32_ARRAY
as u32 },
+ "f64" => quote! { fory_core::types::TypeId::FLOAT64_ARRAY
as u32 },
+ "u8" => quote! { fory_core::types::TypeId::U8 as u32 },
+ "u16" => quote! { fory_core::types::TypeId::U16_ARRAY as
u32 },
+ "u32" => quote! { fory_core::types::TypeId::U32_ARRAY as
u32 },
+ "u64" => quote! { fory_core::types::TypeId::U64_ARRAY as
u32 },
+ "usize" => quote! { fory_core::types::TypeId::USIZE_ARRAY
as u32 },
+ _ => quote! { fory_core::types::TypeId::LIST as u32 },
+ };
+ return quote! {
+ fory_core::meta::FieldType::new(
+ #type_id_token,
+ false,
+ vec![]
+ )
+ };
+ } else {
+ // For non-primitive arrays, use LIST type ID with element
type as generic
+ return quote! {
+ fory_core::meta::FieldType::new(
+ fory_core::types::TypeId::LIST as u32,
+ false,
+ vec![#elem_token]
+ )
+ };
+ }
+ } else {
+ // Array without element type info - shouldn't happen
+ return quote! { compile_error!("Array missing element type"); };
+ }
+ }
+
// If Option, unwrap it before generating children
let (nullable, base_node) = if node.name == "Option" {
if let Some(inner) = node.generics.first() {
@@ -543,7 +600,7 @@ pub(crate) fn get_type_id_by_name(ty: &str) -> u32 {
_ => {}
}
- // Check primitive arrays
+ // Check primitive arrays (Vec)
match ty {
"Vec<bool>" => return TypeId::BOOL_ARRAY as u32,
"Vec<i8>" => return TypeId::INT8_ARRAY as u32,
@@ -560,6 +617,32 @@ pub(crate) fn get_type_id_by_name(ty: &str) -> u32 {
_ => {}
}
+ // Check primitive arrays (fixed-size arrays [T; N])
+ // These will be serialized similarly to Vec but with fixed size
+ if ty.starts_with('[') && ty.contains(';') {
+ // Extract the element type from [T; N]
+ if let Some(elem_ty) = ty.strip_prefix('[').and_then(|s|
s.split(';').next()) {
+ match elem_ty {
+ "bool" => return TypeId::BOOL_ARRAY as u32,
+ "i8" => return TypeId::INT8_ARRAY as u32,
+ "i16" => return TypeId::INT16_ARRAY as u32,
+ "i32" => return TypeId::INT32_ARRAY as u32,
+ "i64" => return TypeId::INT64_ARRAY as u32,
+ "f16" => return TypeId::FLOAT16_ARRAY as u32,
+ "f32" => return TypeId::FLOAT32_ARRAY as u32,
+ "f64" => return TypeId::FLOAT64_ARRAY as u32,
+ "u16" => return TypeId::U16_ARRAY as u32,
+ "u32" => return TypeId::U32_ARRAY as u32,
+ "u64" => return TypeId::U64_ARRAY as u32,
+ "usize" => return TypeId::USIZE_ARRAY as u32,
+ _ => {
+ // Non-primitive array elements, treat as LIST
+ return TypeId::LIST as u32;
+ }
+ }
+ }
+ }
+
// Check collection types
if ty.starts_with("Vec<")
|| ty.starts_with("VecDeque<")
diff --git a/rust/tests/tests/test_array.rs b/rust/tests/tests/test_array.rs
new file mode 100644
index 000000000..8ee088549
--- /dev/null
+++ b/rust/tests/tests/test_array.rs
@@ -0,0 +1,324 @@
+// 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.
+
+use fory_core::fory::Fory;
+use fory_core::register_trait_type;
+use fory_core::serializer::Serializer;
+use fory_derive::ForyObject;
+use std::rc::Rc;
+
+#[test]
+fn test_array_i32() {
+ let fory = Fory::default();
+ let arr = [1, 2, 3, 4, 5];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [i32; 5] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_i64() {
+ let fory = Fory::default();
+ let arr = [100i64, 200, 300];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [i64; 3] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_f64() {
+ let fory = Fory::default();
+ let arr = [1.5, 2.5, 3.5, 4.5];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [f64; 4] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_f32() {
+ let fory = Fory::default();
+ let arr = [1.1f32, 2.2, 3.3];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [f32; 3] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_bool() {
+ let fory = Fory::default();
+ let arr = [true, false, true, false];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [bool; 4] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_i8() {
+ let fory = Fory::default();
+ let arr = [1i8, 2, 3, 4, 5, 6, 7, 8];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [i8; 8] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_i16() {
+ let fory = Fory::default();
+ let arr = [100i16, 200, 300, 400];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [i16; 4] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_string() {
+ let fory = Fory::default();
+ let arr = ["hello".to_string(), "world".to_string(), "fory".to_string()];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [String; 3] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_empty() {
+ let fory = Fory::default();
+ let arr: [i32; 0] = [];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [i32; 0] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_single_element() {
+ let fory = Fory::default();
+ let arr = [42];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [i32; 1] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_large() {
+ let fory = Fory::default();
+ let arr = [1; 100];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [i32; 100] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[derive(ForyObject, PartialEq, Debug)]
+struct Point {
+ x: i32,
+ y: i32,
+}
+
+#[test]
+fn test_array_struct() {
+ let mut fory = Fory::default();
+ fory.register_by_name::<Point>("Point").unwrap();
+
+ let arr = [
+ Point { x: 1, y: 2 },
+ Point { x: 3, y: 4 },
+ Point { x: 5, y: 6 },
+ ];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [Point; 3] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[derive(ForyObject, PartialEq, Debug)]
+struct ArrayStruct {
+ int_array: [i32; 5],
+ float_array: [f64; 3],
+ string_array: [String; 2],
+}
+
+#[test]
+fn test_struct_with_arrays() {
+ let mut fory = Fory::default();
+ fory.register_by_name::<ArrayStruct>("ArrayStruct").unwrap();
+
+ let data = ArrayStruct {
+ int_array: [1, 2, 3, 4, 5],
+ float_array: [1.1, 2.2, 3.3],
+ string_array: ["hello".to_string(), "world".to_string()],
+ };
+
+ let bin = fory.serialize(&data).unwrap();
+ let obj: ArrayStruct = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(data, obj);
+}
+
+#[test]
+fn test_array_nested() {
+ let fory = Fory::default();
+ let arr = [[1, 2], [3, 4], [5, 6]];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [[i32; 2]; 3] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_option() {
+ let fory = Fory::default();
+ let arr = [Some(1), None, Some(3)];
+ let bin = fory.serialize(&arr).unwrap();
+ let obj: [Option<i32>; 3] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(arr, obj);
+}
+
+#[test]
+fn test_array_vec_compatibility() {
+ // Test that an array can be serialized and deserialized as a Vec
+ let fory = Fory::default();
+ let arr = [1, 2, 3, 4, 5];
+ let bin = fory.serialize(&arr).unwrap();
+ // Deserialize as Vec should work since they use the same type ID
+ let vec: Vec<i32> = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(&arr[..], &vec[..]);
+}
+
+#[test]
+fn test_vec_array_compatibility() {
+ // Test that a Vec can be serialized and deserialized as an array
+ let fory = Fory::default();
+ let vec = vec![1, 2, 3];
+ let bin = fory.serialize(&vec).unwrap();
+ // Deserialize as array should work if the size matches
+ let arr: [i32; 3] = fory.deserialize(&bin).expect("deserialize");
+ assert_eq!(&vec[..], &arr[..]);
+}
+
+#[test]
+fn test_array_size_mismatch() {
+ // Test that deserializing with wrong size fails gracefully
+ let fory = Fory::default();
+ let arr = [1, 2, 3, 4, 5];
+ let bin = fory.serialize(&arr).unwrap();
+ // Try to deserialize as array with wrong size
+ let result: Result<[i32; 3], _> = fory.deserialize(&bin);
+ assert!(result.is_err());
+}
+
+// Trait object tests
+
+trait Shape: Serializer {
+ fn area(&self) -> f64;
+ fn name(&self) -> &str;
+}
+
+#[derive(ForyObject, Debug, PartialEq)]
+struct Circle {
+ radius: f64,
+}
+
+impl Shape for Circle {
+ fn area(&self) -> f64 {
+ std::f64::consts::PI * self.radius * self.radius
+ }
+ fn name(&self) -> &str {
+ "Circle"
+ }
+}
+
+#[derive(ForyObject, Debug, PartialEq)]
+struct Rectangle {
+ width: f64,
+ height: f64,
+}
+
+impl Shape for Rectangle {
+ fn area(&self) -> f64 {
+ self.width * self.height
+ }
+ fn name(&self) -> &str {
+ "Rectangle"
+ }
+}
+
+register_trait_type!(Shape, Circle, Rectangle);
+
+#[test]
+fn test_array_box_trait_objects() {
+ let mut fory = Fory::default().compatible(true);
+ fory.register::<Circle>(9001).unwrap();
+ fory.register::<Rectangle>(9002).unwrap();
+
+ // Create an array of Box<dyn Shape>
+ let shapes: [Box<dyn Shape>; 3] = [
+ Box::new(Circle { radius: 5.0 }),
+ Box::new(Rectangle {
+ width: 4.0,
+ height: 6.0,
+ }),
+ Box::new(Circle { radius: 3.0 }),
+ ];
+
+ // Calculate expected areas before serialization
+ let expected_areas: [f64; 3] = [shapes[0].area(), shapes[1].area(),
shapes[2].area()];
+ let expected_names: [&str; 3] = [shapes[0].name(), shapes[1].name(),
shapes[2].name()];
+
+ let bin = fory.serialize(&shapes).unwrap();
+ let deserialized: [Box<dyn Shape>; 3] =
fory.deserialize(&bin).expect("deserialize");
+
+ // Verify the trait methods work correctly
+ assert_eq!(deserialized.len(), 3);
+ for i in 0..3 {
+ assert_eq!(deserialized[i].area(), expected_areas[i]);
+ assert_eq!(deserialized[i].name(), expected_names[i]);
+ }
+}
+
+#[test]
+fn test_array_rc_trait_objects() {
+ let mut fory = Fory::default().compatible(true);
+ fory.register::<Circle>(9001).unwrap();
+ fory.register::<Rectangle>(9002).unwrap();
+
+ // Create Rc<dyn Shape> instances and convert to wrappers
+ let circle1: Rc<dyn Shape> = Rc::new(Circle { radius: 2.0 });
+ let rect: Rc<dyn Shape> = Rc::new(Rectangle {
+ width: 3.0,
+ height: 4.0,
+ });
+ let circle2: Rc<dyn Shape> = Rc::new(Circle { radius: 7.0 });
+
+ // Convert to wrapper types for serialization
+ let shapes: [ShapeRc; 3] = [
+ ShapeRc::from(circle1),
+ ShapeRc::from(rect),
+ ShapeRc::from(circle2),
+ ];
+
+ // Calculate expected areas
+ let expected_areas: [f64; 3] = [
+ std::f64::consts::PI * 4.0, // Circle radius 2.0
+ 12.0, // Rectangle 3x4
+ std::f64::consts::PI * 49.0, // Circle radius 7.0
+ ];
+
+ let bin = fory.serialize(&shapes).unwrap();
+ let deserialized: [ShapeRc; 3] =
fory.deserialize(&bin).expect("deserialize");
+
+ // Verify by unwrapping and calling trait methods
+ assert_eq!(deserialized.len(), 3);
+ for i in 0..3 {
+ let shape: Rc<dyn Shape> = deserialized[i].clone().into();
+ assert!((shape.area() - expected_areas[i]).abs() < 0.001);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]