From 20d217884ca06410b132d9134539122410b1c369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabr=C3=ADcio=20Bracht?= Date: Wed, 24 Dec 2025 11:35:23 -0800 Subject: [PATCH 1/3] eliminate String clone during serialization --- bebytes_derive/src/functional.rs | 10 +-- bebytes_derive/src/structs.rs | 15 ++-- bebytes_derive/src/utils.rs | 120 ------------------------------- 3 files changed, 13 insertions(+), 132 deletions(-) diff --git a/bebytes_derive/src/functional.rs b/bebytes_derive/src/functional.rs index bfaba5a..f67c881 100644 --- a/bebytes_derive/src/functional.rs +++ b/bebytes_derive/src/functional.rs @@ -215,11 +215,13 @@ pub mod pure_helpers { use syn::Ident; /// Create a field accessor without side effects - pub fn create_field_accessor(field_name: &Ident, needs_owned: bool) -> TokenStream { - if needs_owned { - quote! { let #field_name = self.#field_name.clone(); } - } else { + /// For Copy types, pass `is_copy=true` to generate a value copy + /// For non-Copy types, pass `is_copy=false` to generate a reference (avoids clone) + pub fn create_field_accessor(field_name: &Ident, is_copy: bool) -> TokenStream { + if is_copy { quote! { let #field_name = self.#field_name; } + } else { + quote! { let #field_name = &self.#field_name; } } } diff --git a/bebytes_derive/src/structs.rs b/bebytes_derive/src/structs.rs index 6087296..ff18652 100644 --- a/bebytes_derive/src/structs.rs +++ b/bebytes_derive/src/structs.rs @@ -482,7 +482,7 @@ fn process_bits_field_functional( let field_name = &context.field_name; let field_type = context.field_type; - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, false); + let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, true); let bit_sum = crate::functional::pure_helpers::create_bit_sum(size); let limit_check = crate::functional::pure_helpers::create_bit_field_limit_check(field_name, field_type, size); @@ -769,7 +769,7 @@ fn process_primitive_type_functional( let field_size = utils::get_primitive_type_size(field_type)?; - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, false); + let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, true); let bit_sum = crate::functional::pure_helpers::create_byte_bit_sum(field_size); let parsing_tokens = vec![ @@ -1011,7 +1011,7 @@ fn process_vector_functional( let field = context.field; let is_last_field = context.is_last_field; - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, true); + let accessor = quote! { let #field_name = self.#field_name.clone(); }; if let syn::Type::Path(tp) = field_type { if let Some(syn::Type::Path(ref inner_tp)) = utils::solve_for_inner_type(tp, "Vec") { @@ -1340,7 +1340,7 @@ fn process_option_type_functional( let total_size = field_size + 1; let accessor = - crate::functional::pure_helpers::create_field_accessor(field_name, false); + crate::functional::pure_helpers::create_field_accessor(field_name, true); let bit_sum = crate::functional::pure_helpers::create_byte_bit_sum(total_size); let value_parsing = create_option_inner_parsing( @@ -1430,8 +1430,7 @@ fn process_custom_type_functional( let field_name = &context.field_name; let field_type = context.field_type; - let needs_owned = !utils::is_copy(field_type); - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, needs_owned); + let accessor = quote! { let #field_name = self.#field_name.clone(); }; let bit_sum = quote! { bit_sum += 8 * #field_type::field_size(); @@ -1480,7 +1479,7 @@ fn process_string_functional( let field = context.field; let is_last_field = context.is_last_field; - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, true); + let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, false); // Generate parsing code based on size constraints let (bit_sum, parsing, writing) = match (size, string_size_ident) { @@ -1523,7 +1522,7 @@ fn process_size_expression_functional( let field_name = &context.field_name; let field_type = context.field_type; - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, true); + let accessor = quote! { let #field_name = self.#field_name.clone(); }; // Generate the size calculation code let size_calculation = size_expr.generate_evaluation_code(); diff --git a/bebytes_derive/src/utils.rs b/bebytes_derive/src/utils.rs index 9b3a634..bfd0a0e 100644 --- a/bebytes_derive/src/utils.rs +++ b/bebytes_derive/src/utils.rs @@ -138,76 +138,6 @@ pub fn is_vec_of_vec_u8(tp: &syn::TypePath) -> bool { false } -pub(crate) fn is_copy(field_type: &syn::Type) -> bool { - match field_type { - syn::Type::Never(_) | syn::Type::Infer(_) => true, // ! and _ are Copy - - syn::Type::Path(type_path) => { - // Check if it's a known Copy primitive or standard library type - if let Some(ident) = type_path.path.get_ident() { - let name = ident.to_string(); - match name.as_str() { - // Types that are Copy - "bool" | "char" | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" - | "i16" | "i32" | "i64" | "i128" | "isize" | "f32" | "f64" | "NonZero" - | "NonZeroU8" | "NonZeroU16" | "NonZeroU32" | "NonZeroU64" | "NonZeroU128" - | "NonZeroUsize" | "NonZeroI8" | "NonZeroI16" | "NonZeroI32" | "NonZeroI64" - | "NonZeroI128" | "NonZeroIsize" => true, - - _ => false, // Conservatively assume non-Copy - } - } else if !type_path.path.segments.is_empty() { - // Handle generic types - let last_segment = &type_path.path.segments.last().unwrap(); - match last_segment.ident.to_string().as_str() { - "Option" => { - // Option is Copy if T is Copy - if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments { - if !args.args.is_empty() { - if let syn::GenericArgument::Type(ty) = &args.args[0] { - return is_copy(ty); - } - } - } - false - } - "Result" => { - // Result is Copy if both T and E are Copy - if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments { - if args.args.len() >= 2 { - if let syn::GenericArgument::Type(t) = &args.args[0] { - if let syn::GenericArgument::Type(e) = &args.args[1] { - return is_copy(t) && is_copy(e); - } - } - } - } - false - } - // Add more cases for other generic types - _ => false, // Conservatively assume non-Copy - } - } else { - false - } - } - - syn::Type::Array(type_array) => is_copy(&type_array.elem), // Array is Copy if T is Copy - syn::Type::Tuple(type_tuple) => { - // A tuple is Copy if all its elements are Copy - type_tuple.elems.iter().all(is_copy) - } - syn::Type::Paren(type_paren) => is_copy(&type_paren.elem), - syn::Type::Group(type_group) => is_copy(&type_group.elem), - - syn::Type::Reference(type_reference) => { - // &T is always Copy, &mut T is never Copy - type_reference.mutability.is_none() - } - _ => false, // Conservative default for any other types - } -} - #[cfg(test)] mod tests { use super::*; @@ -302,44 +232,6 @@ mod tests { } } - #[test] - fn test_is_copy_trait() { - // Types that implement Copy - let copy_types = vec![ - parse_quote!(u32), - parse_quote!(i64), - parse_quote!([u8; 10]), - parse_quote!(Option), - parse_quote!((u32, u64)), - parse_quote!(Result), - ]; - - for ty in copy_types { - assert!(is_copy(&ty), "Type should implement Copy"); - } - - // Types that don't implement Copy - let non_copy_types = vec![ - parse_quote!(String), - parse_quote!(Vec), - parse_quote!(Option), - parse_quote!(Result), - ]; - - for ty in non_copy_types { - assert!(!is_copy(&ty), "Type should not implement Copy"); - } - } - - #[test] - fn test_is_copy_nested_types() { - // Test nested type checking - assert!(is_copy(&parse_quote!(Option>))); - assert!(!is_copy(&parse_quote!(Option>))); - assert!(is_copy(&parse_quote!(Result<[u8; 10], u32>))); - assert!(!is_copy(&parse_quote!(Result))); - } - #[test] fn test_solve_for_inner_type() { // Test Option inner type extraction @@ -424,21 +316,9 @@ mod tests { #[test] fn test_edge_cases() { - // Test empty path segments let empty_path: syn::Type = parse_quote!(::std::vec::Vec); if let syn::Type::Path(tp) = &empty_path { assert!(!is_primitive_type(tp)); } - - // Test parenthesized types - let paren_ty: syn::Type = parse_quote!((u32)); - assert!(is_copy(&paren_ty)); - - // Test reference types - let ref_ty: syn::Type = parse_quote!(&u32); - assert!(is_copy(&ref_ty)); - - let mut_ref_ty: syn::Type = parse_quote!(&mut u32); - assert!(!is_copy(&mut_ref_ty)); } } From 8700524ef8f7f3d9dcab23182be613a3e77cedbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabr=C3=ADcio=20Bracht?= Date: Wed, 24 Dec 2025 11:43:20 -0800 Subject: [PATCH 2/3] bump versions to 3.0.1 --- bebytes/Cargo.toml | 4 ++-- bebytes_derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bebytes/Cargo.toml b/bebytes/Cargo.toml index 1402afb..47f104a 100644 --- a/bebytes/Cargo.toml +++ b/bebytes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bebytes" -version = "3.0.0" +version = "3.0.1" edition = "2021" rust-version = "1.75.0" license = "MIT" @@ -21,7 +21,7 @@ path = "./bin/performance_benchmark.rs" required-features = ["std"] [dependencies] -bebytes_derive = { version = "3.0.0" } +bebytes_derive = { version = "3.0.1" } bytes = { version = "1", optional = true } [dev-dependencies] diff --git a/bebytes_derive/Cargo.toml b/bebytes_derive/Cargo.toml index 1557aed..d44a545 100644 --- a/bebytes_derive/Cargo.toml +++ b/bebytes_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bebytes_derive" description = "A macro to generate/parse binary representation of messages with custom bit fields" -version = "3.0.0" +version = "3.0.1" edition = "2021" rust-version = "1.75.0" publish = true From 73d03a941b58e3573cffce8fbca34312e27ad9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabr=C3=ADcio=20Bracht?= Date: Wed, 24 Dec 2025 13:19:15 -0800 Subject: [PATCH 3/3] refactor accessor patterns to use AccessorMode enum --- bebytes_derive/src/functional.rs | 20 ++++++++++++-------- bebytes_derive/src/structs.rs | 22 ++++++++++------------ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/bebytes_derive/src/functional.rs b/bebytes_derive/src/functional.rs index f67c881..23aa062 100644 --- a/bebytes_derive/src/functional.rs +++ b/bebytes_derive/src/functional.rs @@ -214,14 +214,18 @@ pub mod pure_helpers { use quote::quote; use syn::Ident; - /// Create a field accessor without side effects - /// For Copy types, pass `is_copy=true` to generate a value copy - /// For non-Copy types, pass `is_copy=false` to generate a reference (avoids clone) - pub fn create_field_accessor(field_name: &Ident, is_copy: bool) -> TokenStream { - if is_copy { - quote! { let #field_name = self.#field_name; } - } else { - quote! { let #field_name = &self.#field_name; } + #[derive(Clone, Copy)] + pub enum AccessorMode { + Copy, + Reference, + Clone, + } + + pub fn create_field_accessor(field_name: &Ident, mode: AccessorMode) -> TokenStream { + match mode { + AccessorMode::Copy => quote! { let #field_name = self.#field_name; }, + AccessorMode::Reference => quote! { let #field_name = &self.#field_name; }, + AccessorMode::Clone => quote! { let #field_name = self.#field_name.clone(); }, } } diff --git a/bebytes_derive/src/structs.rs b/bebytes_derive/src/structs.rs index ff18652..fbcd811 100644 --- a/bebytes_derive/src/structs.rs +++ b/bebytes_derive/src/structs.rs @@ -1,3 +1,4 @@ +use crate::functional::pure_helpers::{create_field_accessor, AccessorMode}; use crate::{attrs, utils}; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; @@ -482,7 +483,7 @@ fn process_bits_field_functional( let field_name = &context.field_name; let field_type = context.field_type; - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, true); + let accessor = create_field_accessor(field_name, AccessorMode::Copy); let bit_sum = crate::functional::pure_helpers::create_bit_sum(size); let limit_check = crate::functional::pure_helpers::create_bit_field_limit_check(field_name, field_type, size); @@ -769,7 +770,7 @@ fn process_primitive_type_functional( let field_size = utils::get_primitive_type_size(field_type)?; - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, true); + let accessor = create_field_accessor(field_name, AccessorMode::Copy); let bit_sum = crate::functional::pure_helpers::create_byte_bit_sum(field_size); let parsing_tokens = vec![ @@ -817,8 +818,7 @@ fn process_array_functional( if let syn::Type::Path(elem) = &*tp.elem { let segments = &elem.path.segments; if segments.len() == 1 && segments[0].ident == "u8" { - let accessor = - crate::functional::pure_helpers::create_field_accessor(field_name, true); + let accessor = create_field_accessor(field_name, AccessorMode::Copy); let bit_sum = crate::functional::pure_helpers::create_byte_bit_sum(length); let parsing = quote! { @@ -1011,7 +1011,7 @@ fn process_vector_functional( let field = context.field; let is_last_field = context.is_last_field; - let accessor = quote! { let #field_name = self.#field_name.clone(); }; + let accessor = create_field_accessor(field_name, AccessorMode::Clone); if let syn::Type::Path(tp) = field_type { if let Some(syn::Type::Path(ref inner_tp)) = utils::solve_for_inner_type(tp, "Vec") { @@ -1250,8 +1250,7 @@ fn process_option_array( let array_len: usize = lit_int.base10_parse()?; let total_size = array_len + 1; - let accessor = - crate::functional::pure_helpers::create_field_accessor(field_name, true); + let accessor = create_field_accessor(field_name, AccessorMode::Copy); let bit_sum = crate::functional::pure_helpers::create_byte_bit_sum(total_size); let parsing = quote! { @@ -1339,8 +1338,7 @@ fn process_option_type_functional( let field_size = utils::get_primitive_type_size(&inner_type)?; let total_size = field_size + 1; - let accessor = - crate::functional::pure_helpers::create_field_accessor(field_name, true); + let accessor = create_field_accessor(field_name, AccessorMode::Copy); let bit_sum = crate::functional::pure_helpers::create_byte_bit_sum(total_size); let value_parsing = create_option_inner_parsing( @@ -1430,7 +1428,7 @@ fn process_custom_type_functional( let field_name = &context.field_name; let field_type = context.field_type; - let accessor = quote! { let #field_name = self.#field_name.clone(); }; + let accessor = create_field_accessor(field_name, AccessorMode::Clone); let bit_sum = quote! { bit_sum += 8 * #field_type::field_size(); @@ -1479,7 +1477,7 @@ fn process_string_functional( let field = context.field; let is_last_field = context.is_last_field; - let accessor = crate::functional::pure_helpers::create_field_accessor(field_name, false); + let accessor = create_field_accessor(field_name, AccessorMode::Reference); // Generate parsing code based on size constraints let (bit_sum, parsing, writing) = match (size, string_size_ident) { @@ -1522,7 +1520,7 @@ fn process_size_expression_functional( let field_name = &context.field_name; let field_type = context.field_type; - let accessor = quote! { let #field_name = self.#field_name.clone(); }; + let accessor = create_field_accessor(field_name, AccessorMode::Clone); // Generate the size calculation code let size_calculation = size_expr.generate_evaluation_code();