From 41b1c30ced6b4bda3373f7bc32faa0be5698ccbf Mon Sep 17 00:00:00 2001 From: goto40 Date: Wed, 14 Jan 2026 19:27:47 +0100 Subject: [PATCH 01/35] chore: prepared test frame --- tests/test_attributes/test_ctx.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index c76d6325..d2c64b8b 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -286,3 +286,30 @@ fn test_enum_endian_ctx() { let ret_write: Vec = ret_read.try_into().unwrap(); assert_eq!(ret_write, test_data) } + +#[test] +fn test_use_implicit_index_of_array() { + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug)] + struct A { + #[deku(temp, temp_value = "items.len().try_into().unwrap()")] + n: u8, + #[deku(count = "n")] + items: Vec, + } + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug)] + struct B { + x: u8, + y: u8, + #[deku(temp, temp_value = "0")] + idx: u8, + } + + let test_data = A { + items: vec![B { x: 8, y: 9 }, B { x: 4, y: 3 }], + }; + + let ret_write: Vec = test_data.try_into().unwrap(); + assert_eq!(vec![2, 8, 9, 0, 4, 3, 0], ret_write); +} From cdba7be096e4de1ec4106ec85fb10933463a2d65 Mon Sep 17 00:00:00 2001 From: goto40 Date: Wed, 14 Jan 2026 19:35:02 +0100 Subject: [PATCH 02/35] chore: prepared test frame --- tests/test_attributes/test_ctx.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index d2c64b8b..108c23d7 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -289,20 +289,26 @@ fn test_enum_endian_ctx() { #[test] fn test_use_implicit_index_of_array() { + #[derive(Debug, Clone, Copy)] + struct IndexContext { + idx: usize, + } + #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug)] struct A { #[deku(temp, temp_value = "items.len().try_into().unwrap()")] n: u8, - #[deku(count = "n")] + #[deku(count = "n", ctx = "IndexContext { idx: 0 }")] items: Vec, } #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug)] + #[deku(ctx = "idx: IndexContext")] struct B { x: u8, y: u8, - #[deku(temp, temp_value = "0")] + #[deku(temp, temp_value = "ctx.idx")] idx: u8, } From ac208483682e09a74cf29ae09a0c28c0ab486f8b Mon Sep 17 00:00:00 2001 From: goto40 Date: Thu, 15 Jan 2026 21:22:06 +0100 Subject: [PATCH 03/35] chore: possible solution --- tests/test_attributes/test_ctx.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index 108c23d7..33a658e4 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -293,29 +293,42 @@ fn test_use_implicit_index_of_array() { struct IndexContext { idx: usize, } - #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug)] struct A { #[deku(temp, temp_value = "items.len().try_into().unwrap()")] n: u8, - #[deku(count = "n", ctx = "IndexContext { idx: 0 }")] + #[deku(count = "n", writer = "write_items(deku::writer, &self.items)")] + //#[deku(count = "n", ctx = "IndexContext { idx: 0 }")] items: Vec, } + + fn write_items( + writer: &mut Writer, + items: &[B], + ) -> Result<(), DekuError> { + for (idx, item) in items.iter().enumerate() { + item.to_writer(writer, IndexContext { idx })?; + } + Ok(()) + } + #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug)] - #[deku(ctx = "idx: IndexContext")] + #[deku(ctx = "ctx: IndexContext", ctx_default = "IndexContext{idx: 0}")] // this struct uses a context for serialization. For deserialization it also works with the default context. struct B { x: u8, y: u8, - #[deku(temp, temp_value = "ctx.idx")] - idx: u8, + #[deku(temp, temp_value = "ctx.idx as u8")] + idx_automatically_filled: u8, } let test_data = A { - items: vec![B { x: 8, y: 9 }, B { x: 4, y: 3 }], + items: vec![B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }], }; let ret_write: Vec = test_data.try_into().unwrap(); - assert_eq!(vec![2, 8, 9, 0, 4, 3, 0], ret_write); + assert_eq!(vec![3, 8, 9, 0, 7, 9, 1, 6, 9, 2], ret_write); + // ^ ^ ^ + // idx=0 idx=1 idx=2 } From b87d5490abce0d1d0b2c77a5c5502e8656b0e832 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 11:43:49 +0100 Subject: [PATCH 04/35] changed Ctx:Copy --> Ctx:Clone --- src/impls/arc.rs | 16 +++++++-------- src/impls/bool.rs | 4 ++-- src/impls/boxed.rs | 16 +++++++-------- src/impls/cow.rs | 8 ++++---- src/impls/cstring.rs | 4 ++-- src/impls/hashmap.rs | 24 +++++++++++----------- src/impls/hashset.rs | 24 +++++++++++----------- src/impls/option.rs | 8 ++++---- src/impls/slice.rs | 16 +++++++-------- src/impls/tuple.rs | 8 ++++---- src/impls/unit.rs | 4 ++-- src/impls/vec.rs | 26 ++++++++++++----------- src/lib.rs | 4 ++-- tests/test_attributes/test_ctx.rs | 34 ++++++++++++++++++++++--------- 14 files changed, 106 insertions(+), 90 deletions(-) diff --git a/src/impls/arc.rs b/src/impls/arc.rs index 92fc5a64..63075b06 100644 --- a/src/impls/arc.rs +++ b/src/impls/arc.rs @@ -10,13 +10,13 @@ use crate::{DekuError, DekuReader, DekuWriter}; impl<'a, T, Ctx> DekuReader<'a, Ctx> for Arc where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, { fn from_reader_with_ctx( reader: &mut Reader, inner_ctx: Ctx, ) -> Result { - let val = ::from_reader_with_ctx(reader, inner_ctx)?; + let val = ::from_reader_with_ctx(reader, inner_ctx.clone())?; Ok(Arc::new(val)) } } @@ -24,7 +24,7 @@ where impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Arc<[T]> where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&T) -> bool, { fn from_reader_with_ctx( @@ -32,7 +32,7 @@ where (limit, inner_ctx): (Limit, Ctx), ) -> Result { // use Vec's implementation and convert to Arc<[T]> - let val = >::from_reader_with_ctx(reader, (limit, inner_ctx))?; + let val = >::from_reader_with_ctx(reader, (limit, inner_ctx.clone()))?; Ok(Arc::from(val.into_boxed_slice())) } } @@ -40,7 +40,7 @@ where impl DekuWriter for Arc<[T]> where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { /// Write all `T`s to bits fn to_writer( @@ -49,7 +49,7 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in self.as_ref() { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } @@ -58,7 +58,7 @@ where impl DekuWriter for Arc where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { /// Write all `T`s to bits fn to_writer( @@ -66,7 +66,7 @@ where writer: &mut Writer, ctx: Ctx, ) -> Result<(), DekuError> { - self.as_ref().to_writer(writer, ctx)?; + self.as_ref().to_writer(writer, ctx.clone())?; Ok(()) } } diff --git a/src/impls/bool.rs b/src/impls/bool.rs index ef082bed..708331ca 100644 --- a/src/impls/bool.rs +++ b/src/impls/bool.rs @@ -6,14 +6,14 @@ use crate::{deku_error, DekuError, DekuReader, DekuWriter}; impl<'a, Ctx> DekuReader<'a, Ctx> for bool where - Ctx: Copy, + Ctx: Clone, u8: DekuReader<'a, Ctx>, { fn from_reader_with_ctx( reader: &mut Reader, inner_ctx: Ctx, ) -> Result { - let val = u8::from_reader_with_ctx(reader, inner_ctx)?; + let val = u8::from_reader_with_ctx(reader, inner_ctx.clone())?; let ret = match val { 0x01 => Ok(true), diff --git a/src/impls/boxed.rs b/src/impls/boxed.rs index 89876a69..29b9da5f 100644 --- a/src/impls/boxed.rs +++ b/src/impls/boxed.rs @@ -11,13 +11,13 @@ use crate::{DekuError, DekuReader, DekuWriter}; impl<'a, T, Ctx> DekuReader<'a, Ctx> for Box where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, { fn from_reader_with_ctx( reader: &mut Reader, inner_ctx: Ctx, ) -> Result { - let val = ::from_reader_with_ctx(reader, inner_ctx)?; + let val = ::from_reader_with_ctx(reader, inner_ctx.clone())?; Ok(Box::new(val)) } } @@ -25,7 +25,7 @@ where impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Box<[T]> where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&T) -> bool, { fn from_reader_with_ctx( @@ -33,7 +33,7 @@ where (limit, inner_ctx): (Limit, Ctx), ) -> Result { // use Vec's implementation and convert to Box<[T]> - let val = >::from_reader_with_ctx(reader, (limit, inner_ctx))?; + let val = >::from_reader_with_ctx(reader, (limit, inner_ctx.clone()))?; Ok(val.into_boxed_slice()) } } @@ -41,7 +41,7 @@ where impl DekuWriter for Box<[T]> where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { /// Write all `T`s to bits fn to_writer( @@ -50,7 +50,7 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in self.as_ref() { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } @@ -59,7 +59,7 @@ where impl DekuWriter for Box where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { /// Write all `T`s to bits fn to_writer( @@ -67,7 +67,7 @@ where writer: &mut Writer, ctx: Ctx, ) -> Result<(), DekuError> { - self.as_ref().to_writer(writer, ctx)?; + self.as_ref().to_writer(writer, ctx.clone())?; Ok(()) } } diff --git a/src/impls/cow.rs b/src/impls/cow.rs index 335aaa0a..de3bc33a 100644 --- a/src/impls/cow.rs +++ b/src/impls/cow.rs @@ -9,13 +9,13 @@ use crate::{DekuError, DekuReader, DekuWriter}; impl<'a, T, Ctx> DekuReader<'a, Ctx> for Cow<'a, T> where T: DekuReader<'a, Ctx> + Clone, - Ctx: Copy, + Ctx: Clone, { fn from_reader_with_ctx( reader: &mut Reader, inner_ctx: Ctx, ) -> Result { - let val = ::from_reader_with_ctx(reader, inner_ctx)?; + let val = ::from_reader_with_ctx(reader, inner_ctx.clone())?; Ok(Cow::Owned(val)) } } @@ -23,7 +23,7 @@ where impl DekuWriter for Cow<'_, T> where T: DekuWriter + Clone, - Ctx: Copy, + Ctx: Clone, { /// Write T from Cow fn to_writer( @@ -31,7 +31,7 @@ where writer: &mut Writer, inner_ctx: Ctx, ) -> Result<(), DekuError> { - (self.borrow() as &T).to_writer(writer, inner_ctx) + (self.borrow() as &T).to_writer(writer, inner_ctx.clone()) } } diff --git a/src/impls/cstring.rs b/src/impls/cstring.rs index 8077bd85..f5d76adc 100644 --- a/src/impls/cstring.rs +++ b/src/impls/cstring.rs @@ -8,7 +8,7 @@ use crate::writer::Writer; use crate::{ctx::*, DekuReader}; use crate::{DekuError, DekuWriter}; -impl DekuWriter for CString +impl DekuWriter for CString where u8: DekuWriter, { @@ -18,7 +18,7 @@ where ctx: Ctx, ) -> Result<(), DekuError> { let bytes = self.as_bytes_with_nul(); - bytes.to_writer(writer, ctx) + bytes.to_writer(writer, ctx.clone()) } } diff --git a/src/impls/hashmap.rs b/src/impls/hashmap.rs index 9ebff2e1..ba1a9878 100644 --- a/src/impls/hashmap.rs +++ b/src/impls/hashmap.rs @@ -25,7 +25,7 @@ where K: DekuReader<'a, Ctx> + Eq + Hash, V: DekuReader<'a, Ctx>, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(usize, &(K, V)) -> bool, { let mut res = HashMap::with_capacity_and_hasher(capacity.unwrap_or(0), S::default()); @@ -34,7 +34,7 @@ where let orig_bits_read = reader.bits_read; while !found_predicate { - let val = <(K, V)>::from_reader_with_ctx(reader, ctx)?; + let val = <(K, V)>::from_reader_with_ctx(reader, ctx.clone())?; found_predicate = predicate(reader.bits_read - orig_bits_read, &val); res.insert(val.0, val.1); } @@ -51,7 +51,7 @@ where K: DekuReader<'a, Ctx> + Eq + Hash, V: DekuReader<'a, Ctx>, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, { let mut res = HashMap::with_capacity_and_hasher(capacity.unwrap_or(0), S::default()); @@ -59,7 +59,7 @@ where if reader.end() { break; } - let val = <(K, V)>::from_reader_with_ctx(reader, ctx)?; + let val = <(K, V)>::from_reader_with_ctx(reader, ctx.clone())?; res.insert(val.0, val.1); } @@ -72,7 +72,7 @@ where K: DekuReader<'a, Ctx> + Eq + Hash, V: DekuReader<'a, Ctx>, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&(K, V)) -> bool, { /// Read `T`s until the given limit @@ -120,7 +120,7 @@ where from_reader_with_ctx_hashmap_with_predicate( reader, Some(count), - inner_ctx, + inner_ctx.clone(), move |_, _| { count -= 1; count == 0 @@ -132,7 +132,7 @@ where Limit::Until(mut predicate, _) => from_reader_with_ctx_hashmap_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |_, kv| predicate(kv), ), @@ -148,7 +148,7 @@ where from_reader_with_ctx_hashmap_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |read_bits, _| read_bits == bit_size, ) } @@ -165,13 +165,13 @@ where from_reader_with_ctx_hashmap_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |read_bits, _| read_bits == bit_size, ) } // Read until `reader.end()` is true - Limit::End => from_reader_with_ctx_hashmap_to_end(reader, None, inner_ctx), + Limit::End => from_reader_with_ctx_hashmap_to_end(reader, None, inner_ctx.clone()), } } } @@ -195,7 +195,7 @@ where } } -impl, V: DekuWriter, S, Ctx: Copy> DekuWriter for HashMap { +impl, V: DekuWriter, S, Ctx: Clone> DekuWriter for HashMap { /// Write all `K, V`s in a `HashMap` to bits. /// * **inner_ctx** - The context required by `K, V`. /// @@ -235,7 +235,7 @@ impl, V: DekuWriter, S, Ctx: Copy> DekuWriter for H inner_ctx: Ctx, ) -> Result<(), DekuError> { for kv in self { - kv.to_writer(writer, inner_ctx)?; + kv.to_writer(writer, inner_ctx.clone())?; } Ok(()) } diff --git a/src/impls/hashset.rs b/src/impls/hashset.rs index bd9ba58c..d46c6ec2 100644 --- a/src/impls/hashset.rs +++ b/src/impls/hashset.rs @@ -24,7 +24,7 @@ fn from_reader_with_ctx_hashset_with_predicate<'a, T, S, Ctx, Predicate, R: Read where T: DekuReader<'a, Ctx> + Eq + Hash, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(usize, &T) -> bool, { let mut res = HashSet::with_capacity_and_hasher(capacity.unwrap_or(0), S::default()); @@ -33,7 +33,7 @@ where let orig_bits_read = reader.bits_read; while !found_predicate { - let val = ::from_reader_with_ctx(reader, ctx)?; + let val = ::from_reader_with_ctx(reader, ctx.clone())?; found_predicate = predicate(reader.bits_read - orig_bits_read, &val); res.insert(val); } @@ -49,7 +49,7 @@ fn from_reader_with_ctx_hashset_to_end<'a, T, S, Ctx, R: Read + Seek>( where T: DekuReader<'a, Ctx> + Eq + Hash, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, { let mut res = HashSet::with_capacity_and_hasher(capacity.unwrap_or(0), S::default()); @@ -57,7 +57,7 @@ where if reader.end() { break; } - let val = ::from_reader_with_ctx(reader, ctx)?; + let val = ::from_reader_with_ctx(reader, ctx.clone())?; res.insert(val); } @@ -68,7 +68,7 @@ impl<'a, T, S, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Ha where T: DekuReader<'a, Ctx> + Eq + Hash, S: BuildHasher + Default, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&T) -> bool, { /// Read `T`s until the given limit @@ -105,7 +105,7 @@ where from_reader_with_ctx_hashset_with_predicate( reader, Some(count), - inner_ctx, + inner_ctx.clone(), move |_, _| { count -= 1; count == 0 @@ -117,7 +117,7 @@ where Limit::Until(mut predicate, _) => from_reader_with_ctx_hashset_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |_, value| predicate(value), ), @@ -133,7 +133,7 @@ where from_reader_with_ctx_hashset_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |read_bits, _| read_bits == bit_size, ) } @@ -150,13 +150,13 @@ where from_reader_with_ctx_hashset_with_predicate( reader, None, - inner_ctx, + inner_ctx.clone(), move |read_bits, _| read_bits == bit_size, ) } // Read until `reader.end()` is true - Limit::End => from_reader_with_ctx_hashset_to_end(reader, None, inner_ctx), + Limit::End => from_reader_with_ctx_hashset_to_end(reader, None, inner_ctx.clone()), } } } @@ -176,7 +176,7 @@ impl<'a, T: DekuReader<'a> + Eq + Hash, S: BuildHasher + Default, Predicate: FnM } } -impl, S, Ctx: Copy> DekuWriter for HashSet { +impl, S, Ctx: Clone> DekuWriter for HashSet { /// Write all `T`s in a `HashSet` to bits. /// * **inner_ctx** - The context required by `T`. /// @@ -205,7 +205,7 @@ impl, S, Ctx: Copy> DekuWriter for HashSet { inner_ctx: Ctx, ) -> Result<(), DekuError> { for v in self { - v.to_writer(writer, inner_ctx)?; + v.to_writer(writer, inner_ctx.clone())?; } Ok(()) } diff --git a/src/impls/option.rs b/src/impls/option.rs index 60acf368..ef5a426c 100644 --- a/src/impls/option.rs +++ b/src/impls/option.rs @@ -2,24 +2,24 @@ use no_std_io::io::{Read, Seek, Write}; use crate::{writer::Writer, DekuError, DekuReader, DekuWriter}; -impl<'a, T: DekuReader<'a, Ctx>, Ctx: Copy> DekuReader<'a, Ctx> for Option { +impl<'a, T: DekuReader<'a, Ctx>, Ctx: Clone> DekuReader<'a, Ctx> for Option { fn from_reader_with_ctx( reader: &mut crate::reader::Reader, inner_ctx: Ctx, ) -> Result { - let val = ::from_reader_with_ctx(reader, inner_ctx)?; + let val = ::from_reader_with_ctx(reader, inner_ctx.clone())?; Ok(Some(val)) } } -impl, Ctx: Copy> DekuWriter for Option { +impl, Ctx: Clone> DekuWriter for Option { fn to_writer( &self, writer: &mut Writer, inner_ctx: Ctx, ) -> Result<(), DekuError> { self.as_ref() - .map_or(Ok(()), |v| v.to_writer(writer, inner_ctx)) + .map_or(Ok(()), |v| v.to_writer(writer, inner_ctx.clone())) } } diff --git a/src/impls/slice.rs b/src/impls/slice.rs index 1a906ee4..419bd4b2 100644 --- a/src/impls/slice.rs +++ b/src/impls/slice.rs @@ -6,7 +6,7 @@ use crate::{DekuError, DekuReader, DekuWriter}; use core::mem::MaybeUninit; use no_std_io::io::{Read, Seek, Write}; -impl<'a, Ctx: Copy, T, const N: usize> DekuReader<'a, Ctx> for [T; N] +impl<'a, Ctx: Clone, T, const N: usize> DekuReader<'a, Ctx> for [T; N] where T: DekuReader<'a, Ctx>, { @@ -19,7 +19,7 @@ where { let mut array: [MaybeUninit; N] = [const { MaybeUninit::uninit() }; N]; for (n, item) in array.iter_mut().enumerate() { - match T::from_reader_with_ctx(reader, ctx) { + match T::from_reader_with_ctx(reader, ctx.clone()) { Ok(value) => { item.write(value); } @@ -46,7 +46,7 @@ where } } -impl DekuWriter for [T; N] +impl DekuWriter for [T; N] where T: DekuWriter, { @@ -56,13 +56,13 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in self { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } } -impl DekuWriter for &[T] +impl DekuWriter for &[T] where T: DekuWriter, { @@ -72,13 +72,13 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in *self { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } } -impl DekuWriter for [T] +impl DekuWriter for [T] where T: DekuWriter, { @@ -88,7 +88,7 @@ where ctx: Ctx, ) -> Result<(), DekuError> { for v in self { - v.to_writer(writer, ctx)?; + v.to_writer(writer, ctx.clone())?; } Ok(()) } diff --git a/src/impls/tuple.rs b/src/impls/tuple.rs index 7c17b502..e819819f 100644 --- a/src/impls/tuple.rs +++ b/src/impls/tuple.rs @@ -37,7 +37,7 @@ macro_rules! ImplDekuTupleTraits { } } - impl<'a, Ctx: Copy, $($T:DekuReader<'a, Ctx>+Sized),+> DekuReader<'a, Ctx> for ($($T,)+) + impl<'a, Ctx: Clone, $($T:DekuReader<'a, Ctx>+Sized),+> DekuReader<'a, Ctx> for ($($T,)+) { fn from_reader_with_ctx( reader: &mut crate::reader::Reader, @@ -48,20 +48,20 @@ macro_rules! ImplDekuTupleTraits { { let tuple = (); $( - let val = <$T>::from_reader_with_ctx(reader, ctx)?; + let val = <$T>::from_reader_with_ctx(reader, ctx.clone())?; let tuple = tuple.append(val); )+ Ok(tuple) } } - impl),+> DekuWriter for ($($T,)+) + impl),+> DekuWriter for ($($T,)+) { #[allow(non_snake_case)] fn to_writer(&self, writer: &mut Writer, ctx: Ctx) -> Result<(), DekuError> { let ($(ref $T,)+) = *self; $( - $T.to_writer(writer, ctx)?; + $T.to_writer(writer, ctx.clone())?; )+ Ok(()) } diff --git a/src/impls/unit.rs b/src/impls/unit.rs index e2d1f5f5..86e469cc 100644 --- a/src/impls/unit.rs +++ b/src/impls/unit.rs @@ -2,7 +2,7 @@ use no_std_io::io::{Read, Seek, Write}; use crate::{reader::Reader, writer::Writer, DekuError, DekuReader, DekuWriter}; -impl DekuReader<'_, Ctx> for () { +impl DekuReader<'_, Ctx> for () { fn from_reader_with_ctx( _reader: &mut Reader, _inner_ctx: Ctx, @@ -11,7 +11,7 @@ impl DekuReader<'_, Ctx> for () { } } -impl DekuWriter for () { +impl DekuWriter for () { /// NOP on write fn to_writer( &self, diff --git a/src/impls/vec.rs b/src/impls/vec.rs index 354761a4..d05ddf4e 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -38,7 +38,7 @@ fn reader_vec_with_predicate<'a, T, Ctx, Predicate, R: Read + Seek>( ) -> Result, DekuError> where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(usize, &T) -> bool, { // ZST detected, return empty vec @@ -51,7 +51,7 @@ where let start_read = reader.bits_read; loop { - let val = ::from_reader_with_ctx(reader, ctx)?; + let val = ::from_reader_with_ctx(reader, ctx.clone())?; res.push(val); // This unwrap is safe as we are pushing to the vec immediately before it, @@ -71,7 +71,7 @@ fn reader_vec_to_end<'a, T, Ctx, R: Read + Seek>( ) -> Result, DekuError> where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, { // ZST detected, return empty vec if mem::size_of::() == 0 { @@ -83,7 +83,7 @@ where if reader.end() { break; } - let val = ::from_reader_with_ctx(reader, ctx)?; + let val = ::from_reader_with_ctx(reader, ctx.clone())?; res.push(val); } @@ -93,7 +93,7 @@ where impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Vec where T: DekuReader<'a, Ctx>, - Ctx: Copy, + Ctx: Clone, Predicate: FnMut(&T) -> bool, { fn from_reader_with_ctx( @@ -112,7 +112,7 @@ where } // Otherwise, read until we have read `count` elements - reader_vec_with_predicate(reader, Some(count), inner_ctx, move |_, _| { + reader_vec_with_predicate(reader, Some(count), inner_ctx.clone(), move |_, _| { count -= 1; count == 0 }) @@ -120,7 +120,9 @@ where // Read until a given predicate returns true Limit::Until(mut predicate, _) => { - reader_vec_with_predicate(reader, None, inner_ctx, move |_, value| predicate(value)) + reader_vec_with_predicate(reader, None, inner_ctx.clone(), move |_, value| { + predicate(value) + }) } // Read until a given quantity of bits have been read @@ -132,7 +134,7 @@ where return Ok(Vec::new()); } - reader_vec_with_predicate(reader, None, inner_ctx, move |read_bits, _| { + reader_vec_with_predicate(reader, None, inner_ctx.clone(), move |read_bits, _| { read_bits == bit_size }) } @@ -146,12 +148,12 @@ where return Ok(Vec::new()); } - reader_vec_with_predicate(reader, None, inner_ctx, move |read_bits, _| { + reader_vec_with_predicate(reader, None, inner_ctx.clone(), move |read_bits, _| { read_bits == bit_size }) } - Limit::End => reader_vec_to_end(reader, None, inner_ctx), + Limit::End => reader_vec_to_end(reader, None, inner_ctx.clone()), } } } @@ -171,7 +173,7 @@ impl<'a, T: DekuReader<'a>, Predicate: FnMut(&T) -> bool> DekuReader<'a, Limit, Ctx: Copy> DekuWriter for Vec { +impl, Ctx: Clone> DekuWriter for Vec { /// Write all `T`s in a `Vec` to bits. /// * **inner_ctx** - The context required by `T`. /// # Examples @@ -202,7 +204,7 @@ impl, Ctx: Copy> DekuWriter for Vec { inner_ctx: Ctx, ) -> Result<(), DekuError> { for v in self { - v.to_writer(writer, inner_ctx)?; + v.to_writer(writer, inner_ctx.clone())?; } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index d1eef61e..b076ffbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -821,7 +821,7 @@ pub trait DekuSize { impl DekuWriter for &T where T: DekuWriter, - Ctx: Copy, + Ctx: Clone, { #[inline(always)] fn to_writer( @@ -829,7 +829,7 @@ where writer: &mut Writer, ctx: Ctx, ) -> Result<(), DekuError> { - ::to_writer(self, writer, ctx)?; + ::to_writer(self, writer, ctx.clone())?; Ok(()) } } diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index 33a658e4..34dbb646 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -289,18 +289,21 @@ fn test_enum_endian_ctx() { #[test] fn test_use_implicit_index_of_array() { - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone)] struct IndexContext { - idx: usize, + idx: std::rc::Rc>, } #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug)] struct A { - #[deku(temp, temp_value = "items.len().try_into().unwrap()")] + #[deku(temp, temp_value = "items567.len().try_into().unwrap()")] n: u8, - #[deku(count = "n", writer = "write_items(deku::writer, &self.items)")] - //#[deku(count = "n", ctx = "IndexContext { idx: 0 }")] - items: Vec, + //#[deku(count = "n", writer = "write_items(deku::writer, &self.items)")] + #[deku( + count = "n", + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)) }" + )] + items567: Vec, } fn write_items( @@ -308,23 +311,34 @@ fn test_use_implicit_index_of_array() { items: &[B], ) -> Result<(), DekuError> { for (idx, item) in items.iter().enumerate() { - item.to_writer(writer, IndexContext { idx })?; + item.to_writer( + writer, + IndexContext { + idx: std::rc::Rc::new(std::cell::Cell::new(idx)), + }, + )?; } Ok(()) } #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug)] - #[deku(ctx = "ctx: IndexContext", ctx_default = "IndexContext{idx: 0}")] // this struct uses a context for serialization. For deserialization it also works with the default context. + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. struct B { x: u8, y: u8, - #[deku(temp, temp_value = "ctx.idx as u8")] + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] idx_automatically_filled: u8, } let test_data = A { - items: vec![B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }], + items567: vec![B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }], }; let ret_write: Vec = test_data.try_into().unwrap(); From fa3a19ecf3040ae6e24f90782d7fd26ded0f1145 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 12:20:54 +0100 Subject: [PATCH 05/35] chore: fx demo prepared --- tests/test_attributes/test_ctx.rs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index 34dbb646..2ac3dadb 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -292,40 +292,25 @@ fn test_use_implicit_index_of_array() { #[derive(Debug, Clone)] struct IndexContext { idx: std::rc::Rc>, + n: usize, } #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug)] struct A { #[deku(temp, temp_value = "items567.len().try_into().unwrap()")] n: u8, - //#[deku(count = "n", writer = "write_items(deku::writer, &self.items)")] #[deku( count = "n", - ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)) }" + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 3 }" )] items567: Vec, } - fn write_items( - writer: &mut Writer, - items: &[B], - ) -> Result<(), DekuError> { - for (idx, item) in items.iter().enumerate() { - item.to_writer( - writer, - IndexContext { - idx: std::rc::Rc::new(std::cell::Cell::new(idx)), - }, - )?; - } - Ok(()) - } - #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug)] #[deku( ctx = "ctx: IndexContext", - ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0))}" + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0}" )] // this struct uses a context for serialization. For deserialization it also works with the default context. struct B { x: u8, @@ -335,6 +320,8 @@ fn test_use_implicit_index_of_array() { temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" )] idx_automatically_filled: u8, + #[deku(temp, temp_value = "if ctx.idx.get() < ctx.n {1} else {0}")] + idx_auto_fx: u8, } let test_data = A { From 8b8bf306cfebcb57e917aafee9f9d5a0c338201e Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 14:39:46 +0100 Subject: [PATCH 06/35] chore: fx demo prepared --- tests/test_attributes/test_ctx.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index 2ac3dadb..fff69b2b 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -329,7 +329,8 @@ fn test_use_implicit_index_of_array() { }; let ret_write: Vec = test_data.try_into().unwrap(); - assert_eq!(vec![3, 8, 9, 0, 7, 9, 1, 6, 9, 2], ret_write); - // ^ ^ ^ - // idx=0 idx=1 idx=2 + assert_eq!(vec![3, 8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0], ret_write); + // ^ ^ ^ ^ ^ ^ + // | fx=1 | fx=1 | fx=0 (last) + // idx=0 idx=1 idx=2 } From 8864f8511c45681c113c4fac6dd37187224c3c1a Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 15:05:53 +0100 Subject: [PATCH 07/35] enabled a context for the writer separately. --- deku-derive/src/lib.rs | 17 +++++++++++++++++ deku-derive/src/macros/deku_read.rs | 1 + deku-derive/src/macros/deku_write.rs | 7 ++++++- tests/test_attributes/test_ctx.rs | 3 ++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs index c858272c..8a45236a 100644 --- a/deku-derive/src/lib.rs +++ b/deku-derive/src/lib.rs @@ -595,6 +595,9 @@ struct FieldData { /// context passed to the field ctx: Option>, + /// writer_context passed to the field + writer_ctx: Option>, + /// map field when updating struct update: Option, @@ -680,6 +683,7 @@ impl FieldData { || self.until.is_some() || self.map.is_some() || self.ctx.is_some() + || self.writer_ctx.is_some() || self.update.is_some() || self.reader.is_some() || self.writer.is_some(); @@ -720,6 +724,11 @@ impl FieldData { .map(|s| s.parse_with(Punctuated::parse_terminated)) .transpose() .map_err(|e| e.to_compile_error())?; + let writer_ctx = receiver + .writer_ctx? + .map(|s| s.parse_with(Punctuated::parse_terminated)) + .transpose() + .map_err(|e| e.to_compile_error())?; let data = Self { ident: receiver.ident, @@ -736,6 +745,7 @@ impl FieldData { read_all: receiver.read_all, map: receiver.map?, ctx, + writer_ctx, update: receiver.update?, reader: receiver.reader?, writer: receiver.writer?, @@ -1122,6 +1132,13 @@ struct DekuFieldReceiver { #[darling(default = "default_res_opt", map = "map_option_litstr")] ctx: Result, ReplacementError>, + /// writer_context passed to the field. + /// A comma separated argument list. + // TODO: The type of it should be `Punctuated` + // https://github.com/TedDriggs/darling/pull/98 + #[darling(default = "default_res_opt", map = "map_option_litstr")] + writer_ctx: Result, ReplacementError>, + /// map field when updating struct #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] update: Result, ReplacementError>, diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs index 9d188680..6dcc2ba1 100644 --- a/deku-derive/src/macros/deku_read.rs +++ b/deku-derive/src/macros/deku_read.rs @@ -676,6 +676,7 @@ fn emit_field_read( &f.map, &f.reader, &f.ctx.as_ref().map(|v| quote!(#v)), + &f.writer_ctx.as_ref().map(|v| quote!(#v)), &f.assert, &f.assert_eq, ]; diff --git a/deku-derive/src/macros/deku_write.rs b/deku-derive/src/macros/deku_write.rs index 24e43650..5eae4c34 100644 --- a/deku-derive/src/macros/deku_write.rs +++ b/deku-derive/src/macros/deku_write.rs @@ -696,6 +696,7 @@ fn emit_field_write( &f.writer, &f.cond, &f.ctx.as_ref().map(|v| quote!(#v)), + &f.writer_ctx.as_ref().map(|v| quote!(#v)), &f.assert, &f.assert_eq, ]; @@ -747,7 +748,11 @@ fn emit_field_write( #[cfg(not(feature = "bits"))] None, f.bytes.as_ref(), - f.ctx.as_ref(), + if f.writer_ctx.is_some() { + f.writer_ctx.as_ref() + } else { + f.ctx.as_ref() + }, field_bit_order, )?; diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index fff69b2b..425331ca 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -301,7 +301,8 @@ fn test_use_implicit_index_of_array() { n: u8, #[deku( count = "n", - ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 3 }" + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0 }", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: (*n).into() }" )] items567: Vec, } From 3563df20c24476aaf6fe59e6dbdd0cdac06b604a Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 15:12:01 +0100 Subject: [PATCH 08/35] doc: attribues --- deku-derive/src/lib.rs | 8 ++++---- src/attributes.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs index 8a45236a..c20245c7 100644 --- a/deku-derive/src/lib.rs +++ b/deku-derive/src/lib.rs @@ -592,10 +592,10 @@ struct FieldData { /// apply a function to the field after it's read map: Option, - /// context passed to the field + /// context passed to the field (reader/writer; see writer_ctx) ctx: Option>, - /// writer_context passed to the field + /// writer_context passed to the field (overrides ctx) writer_ctx: Option>, /// map field when updating struct @@ -1125,14 +1125,14 @@ struct DekuFieldReceiver { #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] map: Result, ReplacementError>, - /// context passed to the field. + /// context passed to the field (reader/writer). See writer_ctx. /// A comma separated argument list. // TODO: The type of it should be `Punctuated` // https://github.com/TedDriggs/darling/pull/98 #[darling(default = "default_res_opt", map = "map_option_litstr")] ctx: Result, ReplacementError>, - /// writer_context passed to the field. + /// writer_context passed to the field. Overrides ctx. /// A comma separated argument list. // TODO: The type of it should be `Punctuated` // https://github.com/TedDriggs/darling/pull/98 diff --git a/src/attributes.rs b/src/attributes.rs index 40a2fd1f..71f2f17c 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -61,7 +61,8 @@ enum DekuEnum { | [map](#map) | field | Specify a function or lambda to apply to the result of the read | [reader](#readerwriter) | variant, field | Custom reader code | [writer](#readerwriter) | variant, field | Custom writer code -| [ctx](#ctx) | top-level, field| Context list for context sensitive parsing +| [ctx](#ctx) | top-level, field| Context list for context sensitive parsing (reader/writer) +| [writer_ctx](#ctx) | top-level, field| Context list for context sensitive parsing (overrides ctx for the writer part) | [ctx_default](#ctx_default) | top-level, field| Default context values | enum: [id](#id) | top-level, variant | enum or variant id value | enum: [id_endian](#id_endian) | top-level | Endianness of *just* the enum `id` From ab21244e7a2acac2fba80773a8af53731f5b26d2 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 15:19:58 +0100 Subject: [PATCH 09/35] prepared new attribute --- deku-derive/src/lib.rs | 14 ++++++++++++-- deku-derive/src/macros/deku_read.rs | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs index c20245c7..40087a2d 100644 --- a/deku-derive/src/lib.rs +++ b/deku-derive/src/lib.rs @@ -583,9 +583,12 @@ struct FieldData { /// tokens providing the number of bytes for the length of the container bytes_read: Option, - /// a predicate to decide when to stop reading elements into the container + /// a predicate to decide when to stop reading elements into the container (receives the current container element) until: Option, + /// a predicate to decide when to stop reading elements into the container (receives the current container element and a clone of the context) + until_with_ctx: Option, + /// read until `reader.end()` read_all: bool, @@ -681,6 +684,7 @@ impl FieldData { any_option_set = any_option_set || self.bytes_read.is_some() || self.until.is_some() + || self.until_with_ctx.is_some() || self.map.is_some() || self.ctx.is_some() || self.writer_ctx.is_some() @@ -742,6 +746,7 @@ impl FieldData { bits_read: receiver.bits_read?, bytes_read: receiver.bytes_read?, until: receiver.until?, + until_with_ctx: receiver.until_with_ctx?, read_all: receiver.read_all, map: receiver.map?, ctx, @@ -834,12 +839,13 @@ impl FieldData { #[cfg(feature = "bits")] if data.read_all && (data.until.is_some() + || data.until_with_ctx.is_some() || data.count.is_some() || (data.bits_read.is_some() || data.bytes_read.is_some())) { return Err(cerror( data.read_all.span(), - "conflicting: `read_all` cannot be used with `until`, `count`, `bits_read`, or `bytes_read`", + "conflicting: `read_all` cannot be used with `until`, `until_with_ctx`, `count`, `bits_read`, or `bytes_read`", )); } @@ -1117,6 +1123,10 @@ struct DekuFieldReceiver { #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] until: Result, ReplacementError>, + /// a predicate to decide when to stop reading elements into the container + #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] + until_with_ctx: Result, ReplacementError>, + /// read until `reader.end()` #[darling(default)] read_all: bool, diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs index 6dcc2ba1..f10cef5f 100644 --- a/deku-derive/src/macros/deku_read.rs +++ b/deku-derive/src/macros/deku_read.rs @@ -671,6 +671,7 @@ fn emit_field_read( &f.bits_read, &f.bytes_read, &f.until, + &f.until_with_ctx, &f.cond, &f.default, &f.map, @@ -868,6 +869,7 @@ fn emit_field_read( } } } else if let Some(field_until) = &f.until { + // TODO until_with_ctx // We wrap the input into another closure here to enforce that it is actually a callable // Otherwise, an incorrectly passed-in integer could unexpectedly convert into a `Count` limit quote! { From 07a6374b6584ae5cd60088478b61c8d585e01c28 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 21:05:11 +0100 Subject: [PATCH 10/35] chore: working demo --- deku-derive/src/lib.rs | 9 +++ deku-derive/src/macros/deku_read.rs | 20 +++++- src/ctx.rs | 48 ++++++++++--- src/impls/arc.rs | 8 ++- src/impls/boxed.rs | 8 ++- src/impls/hashmap.rs | 66 ++++++++++++++++-- src/impls/hashset.rs | 75 ++++++++++++++++---- src/impls/vec.rs | 102 +++++++++++++++++++++++++--- tests/test_attributes/test_ctx.rs | 44 +++++++----- 9 files changed, 317 insertions(+), 63 deletions(-) diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs index 40087a2d..8b42b8f4 100644 --- a/deku-derive/src/lib.rs +++ b/deku-derive/src/lib.rs @@ -633,6 +633,9 @@ struct FieldData { /// write given value of temp field temp_value: Option, + /// post_process + read_post_processing: Option, + /// default value code when used with skip or cond default: Option, @@ -708,6 +711,7 @@ impl FieldData { any_option_set = any_option_set || self.pad_bytes_after.is_some() || self.temp_value.is_some() + || self.read_post_processing.is_some() || self.cond.is_some() || self.assert.is_some() || self.assert_eq.is_some() @@ -763,6 +767,7 @@ impl FieldData { pad_bytes_after: receiver.pad_bytes_after?, temp: receiver.temp, temp_value: receiver.temp_value?, + read_post_processing: receiver.read_post_processing?, default: receiver.default?, cond: receiver.cond?, assert: receiver.assert?, @@ -1191,6 +1196,10 @@ struct DekuFieldReceiver { #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] temp_value: Result, ReplacementError>, + /// post process read values + #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] + read_post_processing: Result, ReplacementError>, + /// default value code when used with skip #[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")] default: Result, ReplacementError>, diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs index f10cef5f..655787f2 100644 --- a/deku-derive/src/macros/deku_read.rs +++ b/deku-derive/src/macros/deku_read.rs @@ -675,6 +675,7 @@ fn emit_field_read( &f.cond, &f.default, &f.map, + &f.read_post_processing, &f.reader, &f.ctx.as_ref().map(|v| quote!(#v)), &f.writer_ctx.as_ref().map(|v| quote!(#v)), @@ -768,6 +769,13 @@ fn emit_field_read( quote! {} }; + let read_post_processing = if f.read_post_processing.is_some() { + let code = &f.read_post_processing.clone().unwrap(); + code.clone() + } else { + quote! {} + }; + let magic_read = if let Some(magic) = &f.magic { emit_magic_read_lit(&crate_, magic) } else { @@ -869,7 +877,6 @@ fn emit_field_read( } } } else if let Some(field_until) = &f.until { - // TODO until_with_ctx // We wrap the input into another closure here to enforce that it is actually a callable // Otherwise, an incorrectly passed-in integer could unexpectedly convert into a `Count` limit quote! { @@ -879,6 +886,16 @@ fn emit_field_read( (::#crate_::ctx::Limit::new_until(#field_until), (#read_args)) )? } + } else if let Some(field_until_with_ctx) = &f.until_with_ctx { + // We wrap the input into another closure here to enforce that it is actually a callable + // Otherwise, an incorrectly passed-in integer could unexpectedly convert into a `Count` limit + quote! { + #type_as_deku_read::from_reader_with_ctx + ( + __deku_reader, + (::#crate_::ctx::Limit::new_until_with_ctx(#field_until_with_ctx), (#read_args)) + )? + } } else if f.read_all { quote! { { @@ -997,6 +1014,7 @@ fn emit_field_read( }; let #field_ident = &#internal_field_ident; + #read_post_processing #field_assert #field_assert_eq diff --git a/src/ctx.rs b/src/ctx.rs index 34830b85..40106127 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -93,13 +93,16 @@ impl FromStr for Endian { // For details: https://github.com/rust-lang/rust-clippy/issues/9413 /// A limit placed on a container's elements #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] -pub enum Limit bool> { +pub enum Limit bool, Ctx, PredicateWithCtx: FnMut(&T, Ctx) -> bool> { /// Read a specific count of elements Count(usize), /// Read until a given predicate holds true Until(Predicate, PhantomData), + /// Read until a given predicate holds true + UntilWithCtx(PredicateWithCtx, PhantomData, PhantomData), + /// Read until a given quantity of bytes have been read ByteSize(ByteSize), @@ -110,35 +113,52 @@ pub enum Limit bool> { End, } -impl From for Limit bool> { +impl From for Limit bool, Ctx, fn(&T, Ctx) -> bool> { #[inline] fn from(n: usize) -> Self { Limit::Count(n) } } -impl FnMut(&'a T) -> bool> From for Limit { +impl FnMut(&'a T) -> bool> From + for Limit bool> +{ #[inline] fn from(predicate: Predicate) -> Self { Limit::Until(predicate, PhantomData) } } -impl From for Limit bool> { +impl< + T, + Ctx, + Predicate: for<'a> FnMut(&'a T) -> bool, + PredicateWithContext: for<'a> FnMut(&'a T, Ctx) -> bool, + > From for Limit +{ + #[inline] + fn from(predicate_with_ctx: PredicateWithContext) -> Self { + Limit::UntilWithCtx(predicate_with_ctx, PhantomData, PhantomData) + } +} + +impl From for Limit bool, Ctx, fn(&T, Ctx) -> bool> { #[inline] fn from(size: ByteSize) -> Self { Limit::ByteSize(size) } } -impl From for Limit bool> { +impl From for Limit bool, Ctx, fn(&T, Ctx) -> bool> { #[inline] fn from(size: BitSize) -> Self { Limit::BitSize(size) } } -impl FnMut(&'a T) -> bool> Limit { +impl FnMut(&'a T) -> bool> + Limit bool> +{ /// Constructs a new Limit that reads until the given predicate returns true /// The predicate is given a reference to the latest read value and must return /// true to stop reading @@ -148,7 +168,19 @@ impl FnMut(&'a T) -> bool> Limit { } } -impl Limit bool> { +impl FnMut(&'a T, Ctx) -> bool> + Limit bool, Ctx, PredicateWithContext> +{ + /// Constructs a new Limit that reads until the given predicate returns true + /// The predicate is given a reference to the latest read value and must return + /// true to stop reading + #[inline] + pub fn new_until_with_ctx(predicate: PredicateWithContext) -> Self { + predicate.into() + } +} + +impl Limit bool, Ctx, fn(&T, Ctx) -> bool> { /// Read until `reader.end()` is true #[inline] pub fn end() -> Self { @@ -156,7 +188,7 @@ impl Limit bool> { } } -impl Limit bool> { +impl Limit bool, Ctx, fn(&T, Ctx) -> bool> { /// Constructs a new Limit that reads until the given number of elements are read #[inline] pub fn new_count(count: usize) -> Self { diff --git a/src/impls/arc.rs b/src/impls/arc.rs index 63075b06..4b72a12d 100644 --- a/src/impls/arc.rs +++ b/src/impls/arc.rs @@ -21,15 +21,17 @@ where } } -impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Arc<[T]> +impl<'a, T, Ctx, Predicate, PredicateWithContext> + DekuReader<'a, (Limit, Ctx)> for Arc<[T]> where T: DekuReader<'a, Ctx>, Ctx: Clone, Predicate: FnMut(&T) -> bool, + PredicateWithContext: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result { // use Vec's implementation and convert to Arc<[T]> let val = >::from_reader_with_ctx(reader, (limit, inner_ctx.clone()))?; @@ -118,7 +120,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: Limit bool>, expected: Arc<[u16]>, expected_rest_bits: &bitvec::slice::BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/boxed.rs b/src/impls/boxed.rs index 29b9da5f..18bcc36d 100644 --- a/src/impls/boxed.rs +++ b/src/impls/boxed.rs @@ -22,15 +22,17 @@ where } } -impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Box<[T]> +impl<'a, T, Ctx, Predicate, PredicateWithContext> + DekuReader<'a, (Limit, Ctx)> for Box<[T]> where T: DekuReader<'a, Ctx>, Ctx: Clone, Predicate: FnMut(&T) -> bool, + PredicateWithContext: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result { // use Vec's implementation and convert to Box<[T]> let val = >::from_reader_with_ctx(reader, (limit, inner_ctx.clone()))?; @@ -116,7 +118,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: Limit bool>, expected: Box<[u16]>, expected_rest_bits: &bitvec::slice::BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/hashmap.rs b/src/impls/hashmap.rs index ba1a9878..d727f11d 100644 --- a/src/impls/hashmap.rs +++ b/src/impls/hashmap.rs @@ -66,14 +66,15 @@ where Ok(res) } -impl<'a, K, V, S, Ctx, Predicate> DekuReader<'a, (Limit<(K, V), Predicate>, Ctx)> - for HashMap +impl<'a, K, V, S, Ctx, Predicate, PredicateWithContext> + DekuReader<'a, (Limit<(K, V), Predicate, Ctx, PredicateWithContext>, Ctx)> for HashMap where K: DekuReader<'a, Ctx> + Eq + Hash, V: DekuReader<'a, Ctx>, S: BuildHasher + Default, Ctx: Clone, Predicate: FnMut(&(K, V)) -> bool, + PredicateWithContext: FnMut(&(K, V), Ctx) -> bool, { /// Read `T`s until the given limit /// * `limit` - the limiting factor on the amount of `T`s to read @@ -103,7 +104,7 @@ where /// ``` fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - (limit, inner_ctx): (Limit<(K, V), Predicate>, Ctx), + (limit, inner_ctx): (Limit<(K, V), Predicate, Ctx, PredicateWithContext>, Ctx), ) -> Result where Self: Sized, @@ -136,6 +137,16 @@ where move |_, kv| predicate(kv), ), + // Read until a given predicate returns true + Limit::UntilWithCtx(mut predicate, _, _) => { + from_reader_with_ctx_hashmap_with_predicate( + reader, + None, + inner_ctx.clone(), + move |_, kv| predicate(kv, inner_ctx.clone()), + ) + } + // Read until a given quantity of bits have been read Limit::BitSize(size) => { let bit_size = size.0; @@ -176,17 +187,19 @@ where } } -impl<'a, K, V, S, Predicate> DekuReader<'a, Limit<(K, V), Predicate>> for HashMap +impl<'a, K, V, S, Predicate, PredicateWithContext> + DekuReader<'a, Limit<(K, V), Predicate, (), PredicateWithContext>> for HashMap where K: DekuReader<'a> + Eq + Hash, V: DekuReader<'a>, S: BuildHasher + Default, Predicate: FnMut(&(K, V)) -> bool, + PredicateWithContext: FnMut(&(K, V), ()) -> bool, { /// Read `K, V`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - limit: Limit<(K, V), Predicate>, + limit: Limit<(K, V), Predicate, (), PredicateWithContext>, ) -> Result where Self: Sized, @@ -299,7 +312,12 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit<(u8, u8), Predicate>, + limit: Limit< + (u8, u8), + Predicate, + (Endian, BitSize), + fn(&(u8, u8), (Endian, BitSize)) -> bool, + >, expected: FxHashMap, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -312,6 +330,42 @@ mod tests { (limit, (endian, BitSize(bit_size))), ) .unwrap(), + None => { + panic!("unexpected") + } + }; + assert_eq!(expected, res_read); + assert_eq!( + reader.rest(), + expected_rest_bits.iter().by_vals().collect::>() + ); + let mut buf = vec![]; + cursor.read_to_end(&mut buf).unwrap(); + assert_eq!(expected_rest_bytes, buf); + } + + #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0x01, 0xAA, 0, 0, 0xBB].as_ref(), Endian::Little, None, (|kv: &(u8, u8)| kv.0 == 0u8 && kv.1 == 0u8).into(), fxhashmap!{0x01 => 0xAA, 0 => 0}, bits![u8, Msb0;], &[0xbb]), + case::until_empty_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), + case::until_empty_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), + case::until_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(16).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + case::read_all([0x01, 0xAA].as_ref(), Endian::Little, None, Limit::end(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[]), + case::until_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(2).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + case::until_count([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::from(1), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + )] + fn test_hashmap_read_no_bitsize bool + Copy>( + input: &[u8], + endian: Endian, + bit_size: Option, + limit: Limit<(u8, u8), Predicate, Endian, fn(&(u8, u8), Endian) -> bool>, + expected: FxHashMap, + expected_rest_bits: &BitSlice, + expected_rest_bytes: &[u8], + ) { + let mut cursor = Cursor::new(input); + let mut reader = Reader::new(&mut cursor); + let res_read = match bit_size { + Some(_) => panic!("unexpected"), None => { FxHashMap::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap() } diff --git a/src/impls/hashset.rs b/src/impls/hashset.rs index d46c6ec2..e397f080 100644 --- a/src/impls/hashset.rs +++ b/src/impls/hashset.rs @@ -64,12 +64,14 @@ where Ok(res) } -impl<'a, T, S, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for HashSet +impl<'a, T, S, Ctx, Predicate, PredicateWithContext> + DekuReader<'a, (Limit, Ctx)> for HashSet where T: DekuReader<'a, Ctx> + Eq + Hash, S: BuildHasher + Default, Ctx: Clone, Predicate: FnMut(&T) -> bool, + PredicateWithContext: FnMut(&T, Ctx) -> bool, { /// Read `T`s until the given limit /// * `limit` - the limiting factor on the amount of `T`s to read @@ -88,7 +90,7 @@ where /// ``` fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result where Self: Sized, @@ -121,6 +123,16 @@ where move |_, value| predicate(value), ), + // Read until a given predicate returns true + Limit::UntilWithCtx(mut predicate, _, _) => { + from_reader_with_ctx_hashset_with_predicate( + reader, + None, + inner_ctx.clone(), + move |_, value| predicate(value, inner_ctx.clone()), + ) + } + // Read until a given quantity of bits have been read Limit::BitSize(size) => { let bit_size = size.0; @@ -161,13 +173,18 @@ where } } -impl<'a, T: DekuReader<'a> + Eq + Hash, S: BuildHasher + Default, Predicate: FnMut(&T) -> bool> - DekuReader<'a, Limit> for HashSet +impl< + 'a, + T: DekuReader<'a> + Eq + Hash, + S: BuildHasher + Default, + Predicate: FnMut(&T) -> bool, + PredicateWithContext: FnMut(&T, ()) -> bool, + > DekuReader<'a, Limit> for HashSet { /// Read `T`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - limit: Limit, + limit: Limit, ) -> Result where Self: Sized, @@ -230,13 +247,6 @@ mod tests { case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), FxHashSet::default(), bits![u8, Msb0;], &[0xaa]), case::count_1([0xAA, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), case::count_2([0xAA, 0xBB, 0xCC].as_ref(), Endian::Little, Some(8), 2.into(), vec![0xAA, 0xBB].into_iter().collect(), bits![u8, Msb0;], &[0xcc]), - case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, None, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::until_empty_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), - case::until_empty_bytes([0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), - case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(8).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::read_all([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::end(), vec![0xAA, 0xBB].into_iter().collect(), bits![u8, Msb0;], &[]), - case::until_bytes([0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(1).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::until_count([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::from(1), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), case::bits_6([0b0110_1001, 0b1110_1001].as_ref(), Endian::Little, Some(6), 2.into(), vec![0b00_011010, 0b00_011110].into_iter().collect(), bits![u8, Msb0; 1, 0, 0, 1], &[]), #[should_panic(expected = "Parse(\"too much data: container of 8 bits cannot hold 9 bits\")")] case::not_enough_data([].as_ref(), Endian::Little, Some(9), 1.into(), FxHashSet::default(), bits![u8, Msb0;], &[]), @@ -255,7 +265,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: Limit bool>, expected: FxHashSet, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -268,6 +278,41 @@ mod tests { (limit, (endian, BitSize(bit_size))), ) .unwrap(), + None => panic!("unexpected"), + }; + assert_eq!(expected, res_read); + assert_eq!( + reader.rest(), + expected_rest_bits.iter().by_vals().collect::>() + ); + let mut buf = vec![]; + cursor.read_to_end(&mut buf).unwrap(); + assert_eq!(expected_rest_bytes, buf); + } + + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] + #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, None, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::until_empty_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), + case::until_empty_bytes([0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), + case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(8).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::read_all([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::end(), vec![0xAA, 0xBB].into_iter().collect(), bits![u8, Msb0;], &[]), + case::until_bytes([0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(1).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::until_count([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::from(1), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + )] + fn test_hashset_read_no_bitsize bool + Copy>( + input: &[u8], + endian: Endian, + bit_size: Option, + limit: Limit bool>, + expected: FxHashSet, + expected_rest_bits: &BitSlice, + expected_rest_bytes: &[u8], + ) { + let mut cursor = Cursor::new(input); + let mut reader = Reader::new(&mut cursor); + let res_read = match bit_size { + Some(_) => panic!("unexpected"), None => FxHashSet::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(), }; assert_eq!(expected, res_read); @@ -297,6 +342,8 @@ mod tests { .any(|u| v == u))); } + // Limit + //::from(2) // Note: These tests also exist in boxed.rs #[cfg(feature = "bits")] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, @@ -311,7 +358,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: Limit bool>, expected: FxHashSet, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/vec.rs b/src/impls/vec.rs index d05ddf4e..a270b022 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -64,6 +64,44 @@ where Ok(res) } +fn reader_vec_with_predicate_with_context<'a, T, Ctx, PredicateWithContext, R: Read + Seek>( + reader: &mut Reader, + capacity: Option, + ctx: Ctx, + mut predicate: PredicateWithContext, +) -> Result, DekuError> +where + T: DekuReader<'a, Ctx>, + Ctx: Clone, + PredicateWithContext: FnMut(usize, &T, Ctx) -> bool, +{ + // ZST detected, return empty vec + if mem::size_of::() == 0 { + return Ok(Vec::new()); + } + + let mut res = capacity.map_or_else(Vec::new, Vec::with_capacity); + + let start_read = reader.bits_read; + + loop { + let val = ::from_reader_with_ctx(reader, ctx.clone())?; + res.push(val); + + // This unwrap is safe as we are pushing to the vec immediately before it, + // so there will always be a last element + if predicate( + reader.bits_read - start_read, + res.last().unwrap(), + ctx.clone(), + ) { + break; + } + } + + Ok(res) +} + fn reader_vec_to_end<'a, T, Ctx, R: Read + Seek>( reader: &mut crate::reader::Reader, capacity: Option, @@ -90,15 +128,17 @@ where Ok(res) } -impl<'a, T, Ctx, Predicate> DekuReader<'a, (Limit, Ctx)> for Vec +impl<'a, T, Ctx, Predicate, PredicateWithContext> + DekuReader<'a, (Limit, Ctx)> for Vec where T: DekuReader<'a, Ctx>, Ctx: Clone, Predicate: FnMut(&T) -> bool, + PredicateWithContext: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result where Self: Sized, @@ -125,6 +165,14 @@ where }) } + // Read until a given predicate returns true + Limit::UntilWithCtx(mut predicate, _, _) => reader_vec_with_predicate_with_context( + reader, + None, + inner_ctx.clone(), + move |_, value, ctx| predicate(value, ctx), + ), + // Read until a given quantity of bits have been read Limit::BitSize(size) => { let bit_size = size.0; @@ -158,13 +206,17 @@ where } } -impl<'a, T: DekuReader<'a>, Predicate: FnMut(&T) -> bool> DekuReader<'a, Limit> - for Vec +impl< + 'a, + T: DekuReader<'a>, + Predicate: FnMut(&T) -> bool, + PredicateWithContext: FnMut(&T, ()) -> bool, + > DekuReader<'a, Limit> for Vec { /// Read `T`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut Reader, - limit: Limit, + limit: Limit, ) -> Result where Self: Sized, @@ -234,7 +286,7 @@ mod tests { )] fn test_vec_reader_no_ctx bool>( mut input: &[u8], - limit: Limit, + limit: Limit bool>, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -257,9 +309,6 @@ mod tests { case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), vec![], bits![u8, Msb0;], &[0xaa]), case::count_1([0xAA, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]), case::count_2([0xAA, 0xBB, 0xCC].as_ref(), Endian::Little, Some(8), 2.into(), vec![0xAA, 0xBB], bits![u8, Msb0;], &[0xcc]), - case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, None, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0], bits![u8, Msb0;], &[0xbb]), - case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(8).into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]), - case::end([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::end(), vec![0xaa, 0xbb], bits![u8, Msb0;], &[]), case::end_bitsize([0xf0, 0xf0].as_ref(), Endian::Little, Some(4), Limit::end(), vec![0xf, 0x0, 0x0f, 0x0], bits![u8, Msb0;], &[]), case::bits_6([0b0110_1001, 0b1110_1001].as_ref(), Endian::Little, Some(6), 2.into(), vec![0b00_011010, 0b00_011110], bits![u8, Msb0; 1, 0, 0, 1], &[]), #[should_panic(expected = "Parse(\"too much data: container of 8 bits cannot hold 9 bits\")")] @@ -279,7 +328,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: Limit bool>, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -291,6 +340,37 @@ mod tests { Vec::::from_reader_with_ctx(&mut reader, (limit, (endian, BitSize(bit_size)))) .unwrap() } + None => panic!("unexpected"), + }; + assert_eq!(expected, res_read); + assert_eq!( + reader.rest(), + expected_rest_bits.iter().by_vals().collect::>() + ); + let mut buf = vec![]; + cursor.read_to_end(&mut buf).unwrap(); + assert_eq!(expected_rest_bytes, buf); + } + + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] + #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, None, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0], bits![u8, Msb0;], &[0xbb]), + case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(8).into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]), + case::end([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::end(), vec![0xaa, 0xbb], bits![u8, Msb0;], &[]), + )] + fn test_vec_reader_no_bitsize bool>( + input: &[u8], + endian: Endian, + bit_size: Option, + limit: Limit bool>, + expected: Vec, + expected_rest_bits: &BitSlice, + expected_rest_bytes: &[u8], + ) { + let mut cursor = Cursor::new(input); + let mut reader = Reader::new(&mut cursor); + let res_read = match bit_size { + Some(bit_size) => panic!("unexpected"), None => Vec::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(), }; assert_eq!(expected, res_read); @@ -327,7 +407,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit, + limit: Limit bool>, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index 425331ca..bda02288 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -293,25 +293,24 @@ fn test_use_implicit_index_of_array() { struct IndexContext { idx: std::rc::Rc>, n: usize, + fx: std::rc::Rc>, } #[deku_derive(DekuRead, DekuWrite)] - #[derive(PartialEq, Debug)] + #[derive(PartialEq, Debug, Clone)] struct A { - #[deku(temp, temp_value = "items567.len().try_into().unwrap()")] - n: u8, #[deku( - count = "n", - ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0 }", - writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: (*n).into() }" + until_with_ctx = "|_:&B,ctx:IndexContext| !ctx.fx.get()", + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" )] - items567: Vec, + items: Vec, } #[deku_derive(DekuRead, DekuWrite)] - #[derive(PartialEq, Debug)] + #[derive(PartialEq, Debug, Clone)] #[deku( ctx = "ctx: IndexContext", - ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0}" + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" )] // this struct uses a context for serialization. For deserialization it also works with the default context. struct B { x: u8, @@ -321,17 +320,28 @@ fn test_use_implicit_index_of_array() { temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" )] idx_automatically_filled: u8, - #[deku(temp, temp_value = "if ctx.idx.get() < ctx.n {1} else {0}")] - idx_auto_fx: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, } let test_data = A { - items567: vec![B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }], + items: vec![B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }], }; - let ret_write: Vec = test_data.try_into().unwrap(); - assert_eq!(vec![3, 8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0], ret_write); - // ^ ^ ^ ^ ^ ^ - // | fx=1 | fx=1 | fx=0 (last) - // idx=0 idx=1 idx=2 + let ret_write: Vec = test_data.clone().try_into().unwrap(); + assert_eq!(vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0], ret_write); + // ^ ^ ^ ^ ^ ^ + // | fx=1 | fx=1 | fx=0 (last) + // idx=0 idx=1 idx=2 + + let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; + assert_eq!(check_data, test_data); + + // check with fx=0 after the second element: + let check_data = A::from_bytes((&[8, 9, 0, 1, 7, 9, 1, 0], 0)).unwrap().1; + assert_eq!(check_data.items.len(), 2); } From 0564f585c4c8eefbd7162a4aba9362b359f48b5f Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 21:06:49 +0100 Subject: [PATCH 11/35] doc: new attribute --- src/attributes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/attributes.rs b/src/attributes.rs index 71f2f17c..fc629deb 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -48,6 +48,7 @@ enum DekuEnum { | [bytes_read](#bytes_read) | field | Set the field representing the number of bytes to read into a container | [until](#until) | field | Set a predicate returning when to stop reading elements into a container | [read_all](#read_all) | field | Read until [reader.end()] returns `true` +| [read_post_processing](#read_post_processing) | field | Code to postprocess read data (including temp values; can be used to modify the context via interior mutability) | [update](#update) | field | Apply code over the field when `.update()` is called | [temp](#temp) | field | Read the field but exclude it from the struct/enum | [temp_value](#temp_value) | field | Write the field but exclude it from the struct/enum From 1af94709ae12e89b6ade0aadec05bdbc10d1a076 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 21:12:03 +0100 Subject: [PATCH 12/35] chore: fixed test refactoring --- examples/many.rs | 2 +- src/impls/hashmap.rs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/examples/many.rs b/examples/many.rs index 29895f45..92ec2be7 100644 --- a/examples/many.rs +++ b/examples/many.rs @@ -23,7 +23,7 @@ fn main() { let mut binding = Cursor::new(custom.clone()); let mut reader = Reader::new(&mut binding); - let ret = as DekuReader>>::from_reader_with_ctx( + let ret = as DekuReader>>::from_reader_with_ctx( &mut reader, Limit::new_count(10_0000), ); diff --git a/src/impls/hashmap.rs b/src/impls/hashmap.rs index d727f11d..48404bca 100644 --- a/src/impls/hashmap.rs +++ b/src/impls/hashmap.rs @@ -287,13 +287,6 @@ mod tests { case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), FxHashMap::default(), bits![u8, Msb0;], &[0xaa]), case::count_1([0x01, 0xAA, 0x02, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0x02, 0xbb]), case::count_2([0x01, 0xAA, 0x02, 0xBB, 0xBB].as_ref(), Endian::Little, Some(8), 2.into(), fxhashmap!{0x01 => 0xAA, 0x02 => 0xBB}, bits![u8, Msb0;], &[0xbb]), - case::until_null([0x01, 0xAA, 0, 0, 0xBB].as_ref(), Endian::Little, None, (|kv: &(u8, u8)| kv.0 == 0u8 && kv.1 == 0u8).into(), fxhashmap!{0x01 => 0xAA, 0 => 0}, bits![u8, Msb0;], &[0xbb]), - case::until_empty_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), - case::until_empty_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), - case::until_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(16).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), - case::read_all([0x01, 0xAA].as_ref(), Endian::Little, None, Limit::end(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[]), - case::until_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(2).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), - case::until_count([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::from(1), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), case::bits_6([0b0000_0100, 0b1111_0000, 0b1000_0000].as_ref(), Endian::Little, Some(6), 2.into(), fxhashmap!{0x01 => 0x0F, 0x02 => 0}, bits![u8, Msb0;], &[]), #[should_panic(expected = "Parse(\"too much data: container of 8 bits cannot hold 9 bits\")")] case::not_enough_data([].as_ref(), Endian::Little, Some(9), 1.into(), FxHashMap::default(), bits![u8, Msb0;], &[]), From eef3617c12c4d3c0912d483a84dd224dc27f880d Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 21:12:57 +0100 Subject: [PATCH 13/35] doc: tests --- tests/test_attributes/test_ctx.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index bda02288..e6364570 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -338,6 +338,7 @@ fn test_use_implicit_index_of_array() { // | fx=1 | fx=1 | fx=0 (last) // idx=0 idx=1 idx=2 + // read the data back let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; assert_eq!(check_data, test_data); From 236c6b1441171efb11548af4254ba1e90c990f92 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 21:21:26 +0100 Subject: [PATCH 14/35] doc --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1e82cb..8d3eafb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## [Unreleased] +### Added + +- Change the requirement that the context (`Ctx`) must be `Copy` in some call. + It is changed to `Clone`, this allows to add interior mutability in the context + (like `Rc>`). This is no breaking change, since `Copy` implies `Clone`. +- Added an attribute `until_with_ctx`: like `until`, but it also gets the context. +- Added `writer_ctx` for fields: overrides `ctx` for the writer case - this allows + a different context value for reader and writer (the reader gets the original + `ctx`). +- Added an attribute `read_post_processing`: code which is inserted after a field is + read (including a temp field). You have full context access. + ## [0.20.2](https://github.com/sharksforarms/deku/compare/deku-v0.20.1...deku-v0.20.2) - 2025-11-25 ### Other From 16557a709a63b7d81fc8aff708054e90e0595848 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 21:21:44 +0100 Subject: [PATCH 15/35] doc --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d3eafb8..79cc383e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- Change the requirement that the context (`Ctx`) must be `Copy` in some call. +- Changed the requirement that the context (`Ctx`) must be `Copy` in some call. It is changed to `Clone`, this allows to add interior mutability in the context (like `Rc>`). This is no breaking change, since `Copy` implies `Clone`. - Added an attribute `until_with_ctx`: like `until`, but it also gets the context. From a02c824fe8ee7b2374559fc2808daa2b62a242c5 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sun, 18 Jan 2026 21:33:49 +0100 Subject: [PATCH 16/35] doc --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79cc383e..e67f6277 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,16 @@ ### Added -- Changed the requirement that the context (`Ctx`) must be `Copy` in some call. - It is changed to `Clone`, this allows to add interior mutability in the context +- Changed the requirement that the context (`Ctx`) must be `Copy` in some calls. + It is changed to `Clone`, this **allows to add interior mutability** in the context (like `Rc>`). This is no breaking change, since `Copy` implies `Clone`. -- Added an attribute `until_with_ctx`: like `until`, but it also gets the context. +- Added an attribute `until_with_ctx`: **like `until`, but it also gets the context**. - Added `writer_ctx` for fields: overrides `ctx` for the writer case - this allows - a different context value for reader and writer (the reader gets the original + a **different context value for reader and writer** (the reader gets the original `ctx`). - Added an attribute `read_post_processing`: code which is inserted after a field is - read (including a temp field). You have full context access. + read (including a temp field). You have full context access (**and you can modify + the context there**). ## [0.20.2](https://github.com/sharksforarms/deku/compare/deku-v0.20.1...deku-v0.20.2) - 2025-11-25 From 25e14701fbc6096901647749ee23c1d0031ed544 Mon Sep 17 00:00:00 2001 From: goto40 Date: Mon, 19 Jan 2026 19:11:53 +0100 Subject: [PATCH 17/35] chore: removed unused param --- src/impls/vec.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/impls/vec.rs b/src/impls/vec.rs index a270b022..ec99f7e6 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -353,15 +353,14 @@ mod tests { } #[cfg(all(feature = "bits", feature = "descriptive-errors"))] - #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, - case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, None, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0], bits![u8, Msb0;], &[0xbb]), - case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(8).into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]), - case::end([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::end(), vec![0xaa, 0xbb], bits![u8, Msb0;], &[]), + #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0], bits![u8, Msb0;], &[0xbb]), + case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, BitSize(8).into(), vec![0xAA], bits![u8, Msb0;], &[0xbb]), + case::end([0xAA, 0xBB].as_ref(), Endian::Little, Limit::end(), vec![0xaa, 0xbb], bits![u8, Msb0;], &[]), )] fn test_vec_reader_no_bitsize bool>( input: &[u8], endian: Endian, - bit_size: Option, limit: Limit bool>, expected: Vec, expected_rest_bits: &BitSlice, @@ -369,10 +368,7 @@ mod tests { ) { let mut cursor = Cursor::new(input); let mut reader = Reader::new(&mut cursor); - let res_read = match bit_size { - Some(bit_size) => panic!("unexpected"), - None => Vec::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(), - }; + let res_read = Vec::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(); assert_eq!(expected, res_read); assert_eq!( reader.rest(), From 1daab1ecc5a4d71166d95e46e3010265f8d6ea0a Mon Sep 17 00:00:00 2001 From: goto40 Date: Mon, 19 Jan 2026 19:13:43 +0100 Subject: [PATCH 18/35] chore: renamed generic param (Context/Ctx) --- src/ctx.rs | 12 ++++++------ src/impls/arc.rs | 8 ++++---- src/impls/boxed.rs | 8 ++++---- src/impls/hashmap.rs | 16 ++++++++-------- src/impls/hashset.rs | 14 +++++++------- src/impls/vec.rs | 20 ++++++++++---------- tests/test_attributes/test_ctx.rs | 2 +- 7 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/ctx.rs b/src/ctx.rs index 40106127..6c8e077f 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -133,11 +133,11 @@ impl< T, Ctx, Predicate: for<'a> FnMut(&'a T) -> bool, - PredicateWithContext: for<'a> FnMut(&'a T, Ctx) -> bool, - > From for Limit + PredicateWithCtx: for<'a> FnMut(&'a T, Ctx) -> bool, + > From for Limit { #[inline] - fn from(predicate_with_ctx: PredicateWithContext) -> Self { + fn from(predicate_with_ctx: PredicateWithCtx) -> Self { Limit::UntilWithCtx(predicate_with_ctx, PhantomData, PhantomData) } } @@ -168,14 +168,14 @@ impl FnMut(&'a T) -> bool> } } -impl FnMut(&'a T, Ctx) -> bool> - Limit bool, Ctx, PredicateWithContext> +impl FnMut(&'a T, Ctx) -> bool> + Limit bool, Ctx, PredicateWithCtx> { /// Constructs a new Limit that reads until the given predicate returns true /// The predicate is given a reference to the latest read value and must return /// true to stop reading #[inline] - pub fn new_until_with_ctx(predicate: PredicateWithContext) -> Self { + pub fn new_until_with_ctx(predicate: PredicateWithCtx) -> Self { predicate.into() } } diff --git a/src/impls/arc.rs b/src/impls/arc.rs index 4b72a12d..ce2cece2 100644 --- a/src/impls/arc.rs +++ b/src/impls/arc.rs @@ -21,17 +21,17 @@ where } } -impl<'a, T, Ctx, Predicate, PredicateWithContext> - DekuReader<'a, (Limit, Ctx)> for Arc<[T]> +impl<'a, T, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit, Ctx)> for Arc<[T]> where T: DekuReader<'a, Ctx>, Ctx: Clone, Predicate: FnMut(&T) -> bool, - PredicateWithContext: FnMut(&T, Ctx) -> bool, + PredicateWithCtx: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result { // use Vec's implementation and convert to Arc<[T]> let val = >::from_reader_with_ctx(reader, (limit, inner_ctx.clone()))?; diff --git a/src/impls/boxed.rs b/src/impls/boxed.rs index 18bcc36d..9225485b 100644 --- a/src/impls/boxed.rs +++ b/src/impls/boxed.rs @@ -22,17 +22,17 @@ where } } -impl<'a, T, Ctx, Predicate, PredicateWithContext> - DekuReader<'a, (Limit, Ctx)> for Box<[T]> +impl<'a, T, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit, Ctx)> for Box<[T]> where T: DekuReader<'a, Ctx>, Ctx: Clone, Predicate: FnMut(&T) -> bool, - PredicateWithContext: FnMut(&T, Ctx) -> bool, + PredicateWithCtx: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result { // use Vec's implementation and convert to Box<[T]> let val = >::from_reader_with_ctx(reader, (limit, inner_ctx.clone()))?; diff --git a/src/impls/hashmap.rs b/src/impls/hashmap.rs index 48404bca..31dd6ff5 100644 --- a/src/impls/hashmap.rs +++ b/src/impls/hashmap.rs @@ -66,15 +66,15 @@ where Ok(res) } -impl<'a, K, V, S, Ctx, Predicate, PredicateWithContext> - DekuReader<'a, (Limit<(K, V), Predicate, Ctx, PredicateWithContext>, Ctx)> for HashMap +impl<'a, K, V, S, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit<(K, V), Predicate, Ctx, PredicateWithCtx>, Ctx)> for HashMap where K: DekuReader<'a, Ctx> + Eq + Hash, V: DekuReader<'a, Ctx>, S: BuildHasher + Default, Ctx: Clone, Predicate: FnMut(&(K, V)) -> bool, - PredicateWithContext: FnMut(&(K, V), Ctx) -> bool, + PredicateWithCtx: FnMut(&(K, V), Ctx) -> bool, { /// Read `T`s until the given limit /// * `limit` - the limiting factor on the amount of `T`s to read @@ -104,7 +104,7 @@ where /// ``` fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - (limit, inner_ctx): (Limit<(K, V), Predicate, Ctx, PredicateWithContext>, Ctx), + (limit, inner_ctx): (Limit<(K, V), Predicate, Ctx, PredicateWithCtx>, Ctx), ) -> Result where Self: Sized, @@ -187,19 +187,19 @@ where } } -impl<'a, K, V, S, Predicate, PredicateWithContext> - DekuReader<'a, Limit<(K, V), Predicate, (), PredicateWithContext>> for HashMap +impl<'a, K, V, S, Predicate, PredicateWithCtx> + DekuReader<'a, Limit<(K, V), Predicate, (), PredicateWithCtx>> for HashMap where K: DekuReader<'a> + Eq + Hash, V: DekuReader<'a>, S: BuildHasher + Default, Predicate: FnMut(&(K, V)) -> bool, - PredicateWithContext: FnMut(&(K, V), ()) -> bool, + PredicateWithCtx: FnMut(&(K, V), ()) -> bool, { /// Read `K, V`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - limit: Limit<(K, V), Predicate, (), PredicateWithContext>, + limit: Limit<(K, V), Predicate, (), PredicateWithCtx>, ) -> Result where Self: Sized, diff --git a/src/impls/hashset.rs b/src/impls/hashset.rs index e397f080..a1c7fc1f 100644 --- a/src/impls/hashset.rs +++ b/src/impls/hashset.rs @@ -64,14 +64,14 @@ where Ok(res) } -impl<'a, T, S, Ctx, Predicate, PredicateWithContext> - DekuReader<'a, (Limit, Ctx)> for HashSet +impl<'a, T, S, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit, Ctx)> for HashSet where T: DekuReader<'a, Ctx> + Eq + Hash, S: BuildHasher + Default, Ctx: Clone, Predicate: FnMut(&T) -> bool, - PredicateWithContext: FnMut(&T, Ctx) -> bool, + PredicateWithCtx: FnMut(&T, Ctx) -> bool, { /// Read `T`s until the given limit /// * `limit` - the limiting factor on the amount of `T`s to read @@ -90,7 +90,7 @@ where /// ``` fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result where Self: Sized, @@ -178,13 +178,13 @@ impl< T: DekuReader<'a> + Eq + Hash, S: BuildHasher + Default, Predicate: FnMut(&T) -> bool, - PredicateWithContext: FnMut(&T, ()) -> bool, - > DekuReader<'a, Limit> for HashSet + PredicateWithCtx: FnMut(&T, ()) -> bool, + > DekuReader<'a, Limit> for HashSet { /// Read `T`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut crate::reader::Reader, - limit: Limit, + limit: Limit, ) -> Result where Self: Sized, diff --git a/src/impls/vec.rs b/src/impls/vec.rs index ec99f7e6..eb7f79cc 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -64,16 +64,16 @@ where Ok(res) } -fn reader_vec_with_predicate_with_context<'a, T, Ctx, PredicateWithContext, R: Read + Seek>( +fn reader_vec_with_predicate_with_context<'a, T, Ctx, PredicateWithCtx, R: Read + Seek>( reader: &mut Reader, capacity: Option, ctx: Ctx, - mut predicate: PredicateWithContext, + mut predicate: PredicateWithCtx, ) -> Result, DekuError> where T: DekuReader<'a, Ctx>, Ctx: Clone, - PredicateWithContext: FnMut(usize, &T, Ctx) -> bool, + PredicateWithCtx: FnMut(usize, &T, Ctx) -> bool, { // ZST detected, return empty vec if mem::size_of::() == 0 { @@ -128,17 +128,17 @@ where Ok(res) } -impl<'a, T, Ctx, Predicate, PredicateWithContext> - DekuReader<'a, (Limit, Ctx)> for Vec +impl<'a, T, Ctx, Predicate, PredicateWithCtx> + DekuReader<'a, (Limit, Ctx)> for Vec where T: DekuReader<'a, Ctx>, Ctx: Clone, Predicate: FnMut(&T) -> bool, - PredicateWithContext: FnMut(&T, Ctx) -> bool, + PredicateWithCtx: FnMut(&T, Ctx) -> bool, { fn from_reader_with_ctx( reader: &mut Reader, - (limit, inner_ctx): (Limit, Ctx), + (limit, inner_ctx): (Limit, Ctx), ) -> Result where Self: Sized, @@ -210,13 +210,13 @@ impl< 'a, T: DekuReader<'a>, Predicate: FnMut(&T) -> bool, - PredicateWithContext: FnMut(&T, ()) -> bool, - > DekuReader<'a, Limit> for Vec + PredicateWithCtx: FnMut(&T, ()) -> bool, + > DekuReader<'a, Limit> for Vec { /// Read `T`s until the given limit from input for types which don't require context. fn from_reader_with_ctx( reader: &mut Reader, - limit: Limit, + limit: Limit, ) -> Result where Self: Sized, diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index e6364570..bba2abd3 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -288,7 +288,7 @@ fn test_enum_endian_ctx() { } #[test] -fn test_use_implicit_index_of_array() { +fn test_interior_mutability_for_context_read_until_with_ctx() { #[derive(Debug, Clone)] struct IndexContext { idx: std::rc::Rc>, From 3318cfc037c49745a057e238508ee0ea95ca5395 Mon Sep 17 00:00:00 2001 From: goto40 Date: Mon, 19 Jan 2026 19:32:37 +0100 Subject: [PATCH 19/35] chore: removed unused param --- src/impls/hashmap.rs | 40 +++++++++++++++------------------------- src/impls/hashset.rs | 36 +++++++++++++++--------------------- 2 files changed, 30 insertions(+), 46 deletions(-) diff --git a/src/impls/hashmap.rs b/src/impls/hashmap.rs index 31dd6ff5..5254c55c 100644 --- a/src/impls/hashmap.rs +++ b/src/impls/hashmap.rs @@ -317,16 +317,11 @@ mod tests { ) { let mut cursor = Cursor::new(input); let mut reader = Reader::new(&mut cursor); - let res_read = match bit_size { - Some(bit_size) => FxHashMap::::from_reader_with_ctx( - &mut reader, - (limit, (endian, BitSize(bit_size))), - ) - .unwrap(), - None => { - panic!("unexpected") - } - }; + let res_read = FxHashMap::::from_reader_with_ctx( + &mut reader, + (limit, (endian, BitSize(bit_size.unwrap()))), + ) + .unwrap(); assert_eq!(expected, res_read); assert_eq!( reader.rest(), @@ -337,19 +332,18 @@ mod tests { assert_eq!(expected_rest_bytes, buf); } - #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, - case::until_null([0x01, 0xAA, 0, 0, 0xBB].as_ref(), Endian::Little, None, (|kv: &(u8, u8)| kv.0 == 0u8 && kv.1 == 0u8).into(), fxhashmap!{0x01 => 0xAA, 0 => 0}, bits![u8, Msb0;], &[0xbb]), - case::until_empty_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), - case::until_empty_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), - case::until_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(16).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), - case::read_all([0x01, 0xAA].as_ref(), Endian::Little, None, Limit::end(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[]), - case::until_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(2).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), - case::until_count([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::from(1), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0x01, 0xAA, 0, 0, 0xBB].as_ref(), Endian::Little, (|kv: &(u8, u8)| kv.0 == 0u8 && kv.1 == 0u8).into(), fxhashmap!{0x01 => 0xAA, 0 => 0}, bits![u8, Msb0;], &[0xbb]), + case::until_empty_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, BitSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), + case::until_empty_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, ByteSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), + case::until_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, BitSize(16).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + case::read_all([0x01, 0xAA].as_ref(), Endian::Little, Limit::end(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[]), + case::until_bytes([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, ByteSize(2).into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), + case::until_count([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, Limit::from(1), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0xbb]), )] fn test_hashmap_read_no_bitsize bool + Copy>( input: &[u8], endian: Endian, - bit_size: Option, limit: Limit<(u8, u8), Predicate, Endian, fn(&(u8, u8), Endian) -> bool>, expected: FxHashMap, expected_rest_bits: &BitSlice, @@ -357,12 +351,8 @@ mod tests { ) { let mut cursor = Cursor::new(input); let mut reader = Reader::new(&mut cursor); - let res_read = match bit_size { - Some(_) => panic!("unexpected"), - None => { - FxHashMap::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap() - } - }; + let res_read = + FxHashMap::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(); assert_eq!(expected, res_read); assert_eq!( reader.rest(), diff --git a/src/impls/hashset.rs b/src/impls/hashset.rs index a1c7fc1f..b71bd8c7 100644 --- a/src/impls/hashset.rs +++ b/src/impls/hashset.rs @@ -272,14 +272,11 @@ mod tests { ) { let mut cursor = Cursor::new(input); let mut reader = Reader::new(&mut cursor); - let res_read = match bit_size { - Some(bit_size) => FxHashSet::::from_reader_with_ctx( - &mut reader, - (limit, (endian, BitSize(bit_size))), - ) - .unwrap(), - None => panic!("unexpected"), - }; + let res_read = FxHashSet::::from_reader_with_ctx( + &mut reader, + (limit, (endian, BitSize(bit_size.unwrap()))), + ) + .unwrap(); assert_eq!(expected, res_read); assert_eq!( reader.rest(), @@ -291,19 +288,18 @@ mod tests { } #[cfg(all(feature = "bits", feature = "descriptive-errors"))] - #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, - case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, None, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::until_empty_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), - case::until_empty_bytes([0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), - case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, None, BitSize(8).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::read_all([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::end(), vec![0xAA, 0xBB].into_iter().collect(), bits![u8, Msb0;], &[]), - case::until_bytes([0xAA, 0xBB].as_ref(), Endian::Little, None, ByteSize(1).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), - case::until_count([0xAA, 0xBB].as_ref(), Endian::Little, None, Limit::from(1), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, + case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::until_empty_bits([0xAA, 0xBB].as_ref(), Endian::Little, BitSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), + case::until_empty_bytes([0xAA, 0xBB].as_ref(), Endian::Little, ByteSize(0).into(), HashSet::default(), bits![u8, Msb0;], &[0xaa, 0xbb]), + case::until_bits([0xAA, 0xBB].as_ref(), Endian::Little, BitSize(8).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::read_all([0xAA, 0xBB].as_ref(), Endian::Little, Limit::end(), vec![0xAA, 0xBB].into_iter().collect(), bits![u8, Msb0;], &[]), + case::until_bytes([0xAA, 0xBB].as_ref(), Endian::Little, ByteSize(1).into(), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), + case::until_count([0xAA, 0xBB].as_ref(), Endian::Little, Limit::from(1), vec![0xAA].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), )] fn test_hashset_read_no_bitsize bool + Copy>( input: &[u8], endian: Endian, - bit_size: Option, limit: Limit bool>, expected: FxHashSet, expected_rest_bits: &BitSlice, @@ -311,10 +307,8 @@ mod tests { ) { let mut cursor = Cursor::new(input); let mut reader = Reader::new(&mut cursor); - let res_read = match bit_size { - Some(_) => panic!("unexpected"), - None => FxHashSet::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(), - }; + let res_read = + FxHashSet::::from_reader_with_ctx(&mut reader, (limit, (endian))).unwrap(); assert_eq!(expected, res_read); assert_eq!( reader.rest(), From 64fa42aa2a4934e49cba32f6b4fe2c04ab43b19a Mon Sep 17 00:00:00 2001 From: goto40 Date: Mon, 19 Jan 2026 19:44:33 +0100 Subject: [PATCH 20/35] chore: clippy --- src/impls/hashmap.rs | 14 +++++++------- src/impls/hashset.rs | 9 +++++++-- src/impls/vec.rs | 9 +++++++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/impls/hashmap.rs b/src/impls/hashmap.rs index 5254c55c..5aefc8f5 100644 --- a/src/impls/hashmap.rs +++ b/src/impls/hashmap.rs @@ -283,6 +283,9 @@ mod tests { }; ); + type MyLimit = + Limit<(u8, u8), Predicate, (Endian, BitSize), fn(&(u8, u8), (Endian, BitSize)) -> bool>; + #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), FxHashMap::default(), bits![u8, Msb0;], &[0xaa]), case::count_1([0x01, 0xAA, 0x02, 0xBB].as_ref(), Endian::Little, Some(8), 1.into(), fxhashmap!{0x01 => 0xAA}, bits![u8, Msb0;], &[0x02, 0xbb]), @@ -305,12 +308,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit< - (u8, u8), - Predicate, - (Endian, BitSize), - fn(&(u8, u8), (Endian, BitSize)) -> bool, - >, + limit: MyLimit, expected: FxHashMap, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -332,6 +330,8 @@ mod tests { assert_eq!(expected_rest_bytes, buf); } + type MyLimit2 = Limit<(u8, u8), Predicate, Endian, fn(&(u8, u8), Endian) -> bool>; + #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, case::until_null([0x01, 0xAA, 0, 0, 0xBB].as_ref(), Endian::Little, (|kv: &(u8, u8)| kv.0 == 0u8 && kv.1 == 0u8).into(), fxhashmap!{0x01 => 0xAA, 0 => 0}, bits![u8, Msb0;], &[0xbb]), case::until_empty_bits([0x01, 0xAA, 0xBB].as_ref(), Endian::Little, BitSize(0).into(), FxHashMap::default(), bits![u8, Msb0;], &[0x01, 0xaa, 0xbb]), @@ -344,7 +344,7 @@ mod tests { fn test_hashmap_read_no_bitsize bool + Copy>( input: &[u8], endian: Endian, - limit: Limit<(u8, u8), Predicate, Endian, fn(&(u8, u8), Endian) -> bool>, + limit: MyLimit2, expected: FxHashMap, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/hashset.rs b/src/impls/hashset.rs index b71bd8c7..2bade6b8 100644 --- a/src/impls/hashset.rs +++ b/src/impls/hashset.rs @@ -242,6 +242,9 @@ mod tests { use super::*; + type MyLimit = + Limit bool>; + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), FxHashSet::default(), bits![u8, Msb0;], &[0xaa]), @@ -265,7 +268,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit bool>, + limit: MyLimit, expected: FxHashSet, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -287,6 +290,8 @@ mod tests { assert_eq!(expected_rest_bytes, buf); } + type MyLimit2 = Limit bool>; + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0].into_iter().collect(), bits![u8, Msb0;], &[0xbb]), @@ -300,7 +305,7 @@ mod tests { fn test_hashset_read_no_bitsize bool + Copy>( input: &[u8], endian: Endian, - limit: Limit bool>, + limit: MyLimit2, expected: FxHashSet, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/vec.rs b/src/impls/vec.rs index eb7f79cc..000a907b 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -352,6 +352,8 @@ mod tests { assert_eq!(expected_rest_bytes, buf); } + type MyLimit = Limit bool>; + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] #[rstest(input, endian, limit, expected, expected_rest_bits, expected_rest_bytes, case::until_null([0xAA, 0, 0xBB].as_ref(), Endian::Little, (|v: &u8| *v == 0u8).into(), vec![0xAA, 0], bits![u8, Msb0;], &[0xbb]), @@ -361,7 +363,7 @@ mod tests { fn test_vec_reader_no_bitsize bool>( input: &[u8], endian: Endian, - limit: Limit bool>, + limit: MyLimit, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], @@ -389,6 +391,9 @@ mod tests { assert_eq!(expected, writer.inner.into_inner()); } + type MyLimit2 = + Limit bool>; + // Note: These tests also exist in boxed.rs #[cfg(feature = "bits")] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, @@ -403,7 +408,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit bool>, + limit: MyLimit2, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], From 36a8365f0dbb4339a2b71e453302e60f1c44df91 Mon Sep 17 00:00:00 2001 From: goto40 Date: Mon, 19 Jan 2026 19:49:28 +0100 Subject: [PATCH 21/35] chore: clippy --- src/impls/boxed.rs | 5 ++++- src/impls/hashset.rs | 7 ++++--- src/impls/vec.rs | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/impls/boxed.rs b/src/impls/boxed.rs index 9225485b..3183ca8d 100644 --- a/src/impls/boxed.rs +++ b/src/impls/boxed.rs @@ -105,6 +105,9 @@ mod tests { assert_eq!(input.to_vec(), writer.inner.into_inner()); } + type MyLimit = + Limit bool>; + // Note: Copied tests from vec.rs impl #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, case::normal_le([0xAA, 0xBB, 0xCC, 0xDD].as_ref(), Endian::Little, Some(16), 2.into(), vec![0xBBAA, 0xDDCC].into_boxed_slice(), bits![u8, Msb0;], &[], vec![0xAA, 0xBB, 0xCC, 0xDD]), @@ -118,7 +121,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit bool>, + limit: MyLimit, expected: Box<[u16]>, expected_rest_bits: &bitvec::slice::BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/hashset.rs b/src/impls/hashset.rs index 2bade6b8..2951a307 100644 --- a/src/impls/hashset.rs +++ b/src/impls/hashset.rs @@ -341,8 +341,9 @@ mod tests { .any(|u| v == u))); } - // Limit - //::from(2) + type MyLimit3 = + Limit bool>; + // Note: These tests also exist in boxed.rs #[cfg(feature = "bits")] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, @@ -357,7 +358,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit bool>, + limit: MyLimit3, expected: FxHashSet, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/vec.rs b/src/impls/vec.rs index 000a907b..977cf509 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -276,6 +276,8 @@ mod tests { use super::*; + type MyLimit3 = Limit bool>; + #[cfg(feature = "bits")] #[rstest(input, limit, expected, expected_rest_bits, expected_rest_bytes, case::count_0([0xAA].as_ref(), 0.into(), vec![], bits![u8, Msb0;], &[0xaa]), @@ -286,7 +288,7 @@ mod tests { )] fn test_vec_reader_no_ctx bool>( mut input: &[u8], - limit: Limit bool>, + limit: MyLimit3, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], From b88ee09e2d41ee9696c424df4f8a5924be9bb93a Mon Sep 17 00:00:00 2001 From: goto40 Date: Mon, 19 Jan 2026 19:51:55 +0100 Subject: [PATCH 22/35] chore: clippy --- src/impls/arc.rs | 5 ++++- src/impls/vec.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/impls/arc.rs b/src/impls/arc.rs index ce2cece2..375e0583 100644 --- a/src/impls/arc.rs +++ b/src/impls/arc.rs @@ -106,6 +106,9 @@ mod tests { assert_eq!(input.to_vec(), writer.inner.into_inner()); } + type MyLimit = + Limit bool>; + // Note: Copied tests from vec.rs impl #[cfg(feature = "bits")] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, expected_write, @@ -120,7 +123,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit bool>, + limit: MyLimit, expected: Arc<[u16]>, expected_rest_bits: &bitvec::slice::BitSlice, expected_rest_bytes: &[u8], diff --git a/src/impls/vec.rs b/src/impls/vec.rs index 977cf509..e1125a37 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -306,6 +306,9 @@ mod tests { assert_eq!(expected_rest_bytes, buf); } + type MyLimit4 = + Limit bool>; + #[cfg(all(feature = "bits", feature = "descriptive-errors"))] #[rstest(input, endian, bit_size, limit, expected, expected_rest_bits, expected_rest_bytes, case::count_0([0xAA].as_ref(), Endian::Little, Some(8), 0.into(), vec![], bits![u8, Msb0;], &[0xaa]), @@ -330,7 +333,7 @@ mod tests { input: &[u8], endian: Endian, bit_size: Option, - limit: Limit bool>, + limit: MyLimit4, expected: Vec, expected_rest_bits: &BitSlice, expected_rest_bytes: &[u8], From 7758a30ded042644fb2ce687c9674b8c1cb5b0b6 Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 19:36:01 +0100 Subject: [PATCH 23/35] updated reference: new error message --- tests/test_compile/cases/count_read_conflict.stderr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_compile/cases/count_read_conflict.stderr b/tests/test_compile/cases/count_read_conflict.stderr index 9adaa4a7..ba4665e2 100644 --- a/tests/test_compile/cases/count_read_conflict.stderr +++ b/tests/test_compile/cases/count_read_conflict.stderr @@ -22,7 +22,7 @@ error: conflicting: both `count` and `bytes_read` specified on field 23 | #[deku(count = "1", bytes_read = "2")] | ^^^ -error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read`, or `bytes_read` +error: conflicting: `read_all` cannot be used with `until`, `until_with_ctx`, `count`, `bits_read`, or `bytes_read` --> $DIR/count_read_conflict.rs:27:10 | 27 | #[derive(DekuRead)] @@ -30,7 +30,7 @@ error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read` | = note: this error originates in the derive macro `DekuRead` (in Nightly builds, run with -Z macro-backtrace for more info) -error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read`, or `bytes_read` +error: conflicting: `read_all` cannot be used with `until`, `until_with_ctx`, `count`, `bits_read`, or `bytes_read` --> $DIR/count_read_conflict.rs:33:10 | 33 | #[derive(DekuRead)] @@ -38,7 +38,7 @@ error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read` | = note: this error originates in the derive macro `DekuRead` (in Nightly builds, run with -Z macro-backtrace for more info) -error: conflicting: `read_all` cannot be used with `until`, `count`, `bits_read`, or `bytes_read` +error: conflicting: `read_all` cannot be used with `until`, `until_with_ctx`, `count`, `bits_read`, or `bytes_read` --> $DIR/count_read_conflict.rs:39:10 | 39 | #[derive(DekuRead)] From d85632d4b3e8636fd3b8f6ba3b081b2fa744c312 Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 19:47:56 +0100 Subject: [PATCH 24/35] chore: fixed missing use command --- src/impls/arc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/impls/arc.rs b/src/impls/arc.rs index 375e0583..8d2f428e 100644 --- a/src/impls/arc.rs +++ b/src/impls/arc.rs @@ -82,8 +82,8 @@ mod tests { use rstest::rstest; use super::*; - #[cfg(feature = "bits")] use crate::ctx::{BitSize, Endian}; + #[cfg(feature = "bits")] use crate::native_endian; use crate::reader::Reader; #[cfg(feature = "bits")] From 67a0ed4c19bd4fd04591233f443a713f33557b8c Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 19:51:30 +0100 Subject: [PATCH 25/35] chore: fixed missing use command --- src/impls/arc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/impls/arc.rs b/src/impls/arc.rs index 8d2f428e..f2f5adc5 100644 --- a/src/impls/arc.rs +++ b/src/impls/arc.rs @@ -83,7 +83,6 @@ mod tests { use super::*; use crate::ctx::{BitSize, Endian}; - #[cfg(feature = "bits")] use crate::native_endian; use crate::reader::Reader; #[cfg(feature = "bits")] From aec35712294d4a143cb61dd6ac512320395f3e07 Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 20:05:42 +0100 Subject: [PATCH 26/35] chore:added test with hashset --- tests/test_attributes/test_ctx.rs | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index bba2abd3..cffda142 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -1,4 +1,5 @@ use core::convert::{TryFrom, TryInto}; +use std::collections::HashSet; use std::io::Cursor; use deku::prelude::*; @@ -346,3 +347,62 @@ fn test_interior_mutability_for_context_read_until_with_ctx() { let check_data = A::from_bytes((&[8, 9, 0, 1, 7, 9, 1, 0], 0)).unwrap().1; assert_eq!(check_data.items.len(), 2); } + +#[test] +fn test_interior_mutability_for_context_read_until_with_ctx_hashset() { + #[derive(Debug, Clone)] + struct IndexContext { + idx: std::rc::Rc>, + n: usize, + fx: std::rc::Rc>, + } + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone)] + struct A { + #[deku( + until_with_ctx = "|_:&B,ctx:IndexContext| !ctx.fx.get()", + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" + )] + items: HashSet, + } + + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone, Eq, Hash)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, + } + + let test_data = A { + items: HashSet::from([B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }]), + }; + + let ret_write: Vec = test_data.clone().try_into().unwrap(); + let test_bytes = vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0]; + assert_eq!(ret_write.len(), test_bytes.len()); + assert_eq!(ret_write.last().unwrap(), &0); + + // read the data back + let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; + assert_eq!(check_data, test_data); + + // check with fx=0 after the second element: + let check_data = A::from_bytes((&[8, 9, 0, 1, 7, 9, 1, 0], 0)).unwrap().1; + assert_eq!(check_data.items.len(), 2); +} From 8c6d749bf0fe3caa3aa6b86f829135bdea9a752a Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 20:58:20 +0100 Subject: [PATCH 27/35] chore:added test with hashmap --- tests/test_attributes/test_ctx.rs | 59 ++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index cffda142..82fbdcdf 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -1,5 +1,5 @@ use core::convert::{TryFrom, TryInto}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::io::Cursor; use deku::prelude::*; @@ -406,3 +406,60 @@ fn test_interior_mutability_for_context_read_until_with_ctx_hashset() { let check_data = A::from_bytes((&[8, 9, 0, 1, 7, 9, 1, 0], 0)).unwrap().1; assert_eq!(check_data.items.len(), 2); } + +#[test] +fn test_interior_mutability_for_context_read_until_with_ctx_hashmap() { + #[derive(Debug, Clone)] + struct IndexContext { + idx: std::rc::Rc>, + n: usize, + fx: std::rc::Rc>, + } + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone)] + struct A { + #[deku( + until_with_ctx = "|x:&(B,B),ctx:IndexContext| !ctx.fx.get()", + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" + )] + items: HashMap, + } + + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone, Eq, Hash)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, + } + + let test_data = A { + items: HashMap::from([ + (B { x: 8, y: 9 }, B { x: 8, y: 9 }), + (B { x: 8, y: 9 }, B { x: 7, y: 9 }), + (B { x: 8, y: 9 }, B { x: 6, y: 9 }), + ]), + }; + + let ret_write: Vec = test_data.clone().try_into().unwrap(); + assert_eq!(ret_write.last().unwrap(), &0); + + // read the data back + let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; + assert_eq!(check_data, test_data); +} From e34d40d0e4bd4fb26ac31b8fb2c105d37066aba9 Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 21:04:12 +0100 Subject: [PATCH 28/35] chore: reworked example --- tests/test_attributes/test_ctx.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index 82fbdcdf..41293437 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -419,11 +419,11 @@ fn test_interior_mutability_for_context_read_until_with_ctx_hashmap() { #[derive(PartialEq, Debug, Clone)] struct A { #[deku( - until_with_ctx = "|x:&(B,B),ctx:IndexContext| !ctx.fx.get()", + until_with_ctx = "|x:&(N,B),ctx:IndexContext| !ctx.fx.get()", ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" )] - items: HashMap, + items: HashMap, } #[deku_derive(DekuRead, DekuWrite)] @@ -448,11 +448,21 @@ fn test_interior_mutability_for_context_read_until_with_ctx_hashmap() { auto_fx: u8, } + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone, Eq, Hash)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct N { + n: u8, + } + let test_data = A { items: HashMap::from([ - (B { x: 8, y: 9 }, B { x: 8, y: 9 }), - (B { x: 8, y: 9 }, B { x: 7, y: 9 }), - (B { x: 8, y: 9 }, B { x: 6, y: 9 }), + (N { n: 1 }, B { x: 8, y: 9 }), + (N { n: 2 }, B { x: 7, y: 9 }), + (N { n: 3 }, B { x: 6, y: 9 }), ]), }; From 148df77d092e675539f67d5c1d6bc8cf9a8d5994 Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 22:07:45 +0100 Subject: [PATCH 29/35] chore: added test with slice --- tests/test_attributes/test_ctx.rs | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index 41293437..b775c6aa 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -473,3 +473,55 @@ fn test_interior_mutability_for_context_read_until_with_ctx_hashmap() { let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; assert_eq!(check_data, test_data); } + +#[test] +fn test_interior_mutability_for_context_read_until_with_ctx_slice_ref() { + #[derive(Debug, Clone)] + struct IndexContext { + idx: std::rc::Rc>, + n: usize, + fx: std::rc::Rc>, + } + + #[deku_derive(DekuRead, DekuWrite)] + #[derive(PartialEq, Debug, Clone)] + #[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" + )] // this struct uses a context for serialization. For deserialization it also works with the default context. + struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, + } + + let data = [B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }]; + + let mut buf = vec![]; + let mut cursor = Cursor::new(&mut buf); + let mut writer = Writer::new(&mut cursor); + data[..] + .to_writer( + &mut writer, + IndexContext { + idx: std::rc::Rc::new(std::cell::Cell::new(0)), + n: 3, + fx: std::rc::Rc::new(std::cell::Cell::new(false)), + }, + ) + .unwrap(); + assert_eq!(vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0], buf); + // ^ ^ ^ ^ ^ ^ + // | fx=1 | fx=1 | fx=0 (last) + // idx=0 idx=1 idx=2 +} From a478f13675cb4922331eb0a65df9480cc908bd86 Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 22:09:05 +0100 Subject: [PATCH 30/35] chore: reworked example (len instead of fixed number) --- tests/test_attributes/test_ctx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index b775c6aa..ebf9ad61 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -515,7 +515,7 @@ fn test_interior_mutability_for_context_read_until_with_ctx_slice_ref() { &mut writer, IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), - n: 3, + n: data.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)), }, ) From fc8127e3a9d3f381474f6fc058a6da713da2f87b Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 22:12:44 +0100 Subject: [PATCH 31/35] chore: fixed test with slice --- tests/test_attributes/test_ctx.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index ebf9ad61..ba71c07b 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -510,7 +510,8 @@ fn test_interior_mutability_for_context_read_until_with_ctx_slice_ref() { let mut buf = vec![]; let mut cursor = Cursor::new(&mut buf); let mut writer = Writer::new(&mut cursor); - data[..] + let myref = &data[..]; + myref .to_writer( &mut writer, IndexContext { From 6e2690ce7a3a3633c6e56ceba6bca3896efb11a7 Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 23 Jan 2026 22:21:00 +0100 Subject: [PATCH 32/35] chore: reworked example (len from ref) --- tests/test_attributes/test_ctx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index ba71c07b..bd27250b 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -516,7 +516,7 @@ fn test_interior_mutability_for_context_read_until_with_ctx_slice_ref() { &mut writer, IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), - n: data.len(), + n: myref.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)), }, ) From fe10a34185d43c4835ed263e11bf72045949fd9f Mon Sep 17 00:00:00 2001 From: goto40 Date: Sat, 24 Jan 2026 08:52:05 +0100 Subject: [PATCH 33/35] chore: reworked example (auto referencing rules) --- tests/test_attributes/test_ctx.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_attributes/test_ctx.rs b/tests/test_attributes/test_ctx.rs index bd27250b..3e87a851 100644 --- a/tests/test_attributes/test_ctx.rs +++ b/tests/test_attributes/test_ctx.rs @@ -511,7 +511,8 @@ fn test_interior_mutability_for_context_read_until_with_ctx_slice_ref() { let mut cursor = Cursor::new(&mut buf); let mut writer = Writer::new(&mut cursor); let myref = &data[..]; - myref + // auto referencing rules: we want to call the method on `&[T]`: + (&myref) .to_writer( &mut writer, IndexContext { From 925013cfa8948efab2c0fe0b51ad1a808bf5e03f Mon Sep 17 00:00:00 2001 From: goto40 Date: Fri, 13 Feb 2026 21:46:12 +0100 Subject: [PATCH 34/35] doc: added infos on new fields (for the context with interior mutability) --- src/attributes.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/src/attributes.rs b/src/attributes.rs index fc629deb..0f95d1cc 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -47,8 +47,9 @@ enum DekuEnum { | [bits_read](#bits_read) | field | Set the field representing the number of bits to read into a container | [bytes_read](#bytes_read) | field | Set the field representing the number of bytes to read into a container | [until](#until) | field | Set a predicate returning when to stop reading elements into a container +| [until_with_ctx](#context_with_interior_mutability) | field | like `until`, but the predicate also receives the context as second argument | [read_all](#read_all) | field | Read until [reader.end()] returns `true` -| [read_post_processing](#read_post_processing) | field | Code to postprocess read data (including temp values; can be used to modify the context via interior mutability) +| [read_post_processing](#context_with_interior_mutability) | field | Code to postprocess read data (including temp values; can be used to modify the context via interior mutability) | [update](#update) | field | Apply code over the field when `.update()` is called | [temp](#temp) | field | Read the field but exclude it from the struct/enum | [temp_value](#temp_value) | field | Write the field but exclude it from the struct/enum @@ -63,7 +64,7 @@ enum DekuEnum { | [reader](#readerwriter) | variant, field | Custom reader code | [writer](#readerwriter) | variant, field | Custom writer code | [ctx](#ctx) | top-level, field| Context list for context sensitive parsing (reader/writer) -| [writer_ctx](#ctx) | top-level, field| Context list for context sensitive parsing (overrides ctx for the writer part) +| [writer_ctx](#context_with_interior_mutability) | top-level, field| Context list for context sensitive parsing (overrides ctx for the writer part) | [ctx_default](#ctx_default) | top-level, field| Default context values | enum: [id](#id) | top-level, variant | enum or variant id value | enum: [id_endian](#id_endian) | top-level | Endianness of *just* the enum `id` @@ -1156,6 +1157,121 @@ assert_eq!(&*data, value); # fn main() {} ``` +#
Context with Interior Mutability (using `until_with_ctx`, `writer_ctx`, `read_post_processing`)
+ +The following example demonstrates how to read and write an array of data, where a flag in each data item (`auto_fx`) indicates whether that item is the last in a containing array. +**Importantly, this flag is not part of the actual data structure** presented to the user and is only a temporary construct used during reading and writing. +With this this flag is always set correctly and cannot be misused (e.g. be forgotten to be set correctly). + +The central idea is to use a **context with interior mutability** (specifically, the `fx` field) to make the temporary value available to the outer structure while traversing the array. This context can also be used to increment a counter representing the current index of the traversed array. + +**Key Steps Illustrated in This Example:** + +1. **Define a context with interior mutability**: + - An `idx` field representing the **current index of the array**. + - A flag `fx` indicating the **end of the array**. + - A (non-mutable) value `n` representing the **length of the array** (while writing) or zero (while reading). + +2. **Set up the context separately for reading (`ctx`) and writing (`writer_ctx`)**. This is + important, since the final array length is unknown while reading. + +3. **During reading**: + - The temporary information stored in the `auto_fx` field must be passed back to the caller via the context. + - `read_post_processing` stores the temporary value in the context. + - `until_with_ctx` in the surrounding array uses this context to control how much data to read (until the `fx` flag is zero). + +4. **During writing**: + - The index is updated in each step and the `auto_fx` field is set accordingly (last entry is set to `false`). + +Example: + +* Here, the user fills an array with x/y data + ```rust,ignore + A { items: vec![ + B { x: 8, y: 9 }, + B { x: 7, y: 9 }, + B { x: 6, y: 9 }, + ] }; + ``` +* This data is stored on disk as x/y/"index-in-array"/"fx=0 for the last element, else fx=1" + ```rust,ignore + vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0] + // ^ ^ ^ ^ ^ ^ + // | fx=1 | fx=1 | fx=0 (last) + // idx=0 idx=1 idx=2 + ``` +* The user (providing the x/y data) is not aware of the index and the `fx` field + stored on disk (and cannot break the binary data format by providing some wrong data). + +* Note: this enables to transparently implement "Extended Length Data Items" from the + [ASTERIX EUROCONTOL](https://www.eurocontrol.int/publication/eurocontrol-specification-surveillance-data-exchange-part-i) + standard, without making control flags (`fx`) visible in the user data. + +```rust +# #[cfg(feature = "alloc")] +# use deku::prelude::*; +# #[cfg(feature = "alloc")] +# fn main() { +#[derive(Debug, Clone)] +struct IndexContext { + idx: std::rc::Rc>, + n: usize, + fx: std::rc::Rc>, +} +#[deku_derive(DekuRead, DekuWrite)] +#[derive(PartialEq, Debug, Clone)] +struct A { + #[deku( + until_with_ctx = "|_:&B,ctx:IndexContext| !ctx.fx.get()", + ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", + writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" + )] + items: Vec, +} +#[deku_derive(DekuRead, DekuWrite)] +#[derive(PartialEq, Debug, Clone)] +#[deku( + ctx = "ctx: IndexContext", + ctx_default = "IndexContext{idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}" +)] // this struct uses a context for serialization. For deserialization it also works with the default context. +struct B { + x: u8, + y: u8, + #[deku( + temp, + temp_value = "{let ret = ctx.idx.get() as u8; ctx.idx.set(ctx.idx.get()+1); ret}" + )] + idx_automatically_filled: u8, + #[deku( + read_post_processing = "ctx.fx.set(*auto_fx!=0);", + temp, + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + )] + auto_fx: u8, +} +# +# let test_data = A { +# items: vec![B { x: 8, y: 9 }, B { x: 7, y: 9 }, B { x: 6, y: 9 }], +# }; +# +# let ret_write: Vec = test_data.clone().try_into().unwrap(); +# assert_eq!(vec![8, 9, 0, 1, 7, 9, 1, 1, 6, 9, 2, 0], ret_write); +# // ^ ^ ^ ^ ^ ^ +# // | fx=1 | fx=1 | fx=0 (last) +# // idx=0 idx=1 idx=2 +# +# // read the data back +# let check_data = A::from_bytes((&ret_write, 0)).unwrap().1; +# assert_eq!(check_data, test_data); +# +# // check with fx=0 after the second element: +# let check_data = A::from_bytes((&[8, 9, 0, 1, 7, 9, 1, 0], 0)).unwrap().1; +# assert_eq!(check_data.items.len(), 2); +# } +# #[cfg(not(feature = "alloc"))] +# fn main() {} +``` + # update Specify custom code to run on the field when `.update()` is called on the struct/enum From c0e28be1d885efcb961d5740fceee72a6a9f19a6 Mon Sep 17 00:00:00 2001 From: goto40 Date: Sat, 14 Feb 2026 09:21:44 +0100 Subject: [PATCH 35/35] updated docs (reformulated) --- src/attributes.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/attributes.rs b/src/attributes.rs index 0f95d1cc..fe1b196a 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -1161,24 +1161,24 @@ assert_eq!(&*data, value); The following example demonstrates how to read and write an array of data, where a flag in each data item (`auto_fx`) indicates whether that item is the last in a containing array. **Importantly, this flag is not part of the actual data structure** presented to the user and is only a temporary construct used during reading and writing. -With this this flag is always set correctly and cannot be misused (e.g. be forgotten to be set correctly). +This ensures the flag is always set correctly and cannot be misused (e.g. be forgotten to be set correctly). The central idea is to use a **context with interior mutability** (specifically, the `fx` field) to make the temporary value available to the outer structure while traversing the array. This context can also be used to increment a counter representing the current index of the traversed array. **Key Steps Illustrated in This Example:** 1. **Define a context with interior mutability**: - - An `idx` field representing the **current index of the array**. - - A flag `fx` indicating the **end of the array**. - - A (non-mutable) value `n` representing the **length of the array** (while writing) or zero (while reading). + 1. A mutable `idx` field representing the **current index of the array** (mutable through a `Cell`, shared through an `Rc`). + 2. A mutable flag `fx` indicating the **end of the array** (mutable through a `Cell`, shared through an `Rc`). + 3. A (non-mutable) value `n` representing the **length of the array** (while writing) or zero (while reading). 2. **Set up the context separately for reading (`ctx`) and writing (`writer_ctx`)**. This is important, since the final array length is unknown while reading. 3. **During reading**: - - The temporary information stored in the `auto_fx` field must be passed back to the caller via the context. - - `read_post_processing` stores the temporary value in the context. - - `until_with_ctx` in the surrounding array uses this context to control how much data to read (until the `fx` flag is zero). + 1. The temporary information stored in the `auto_fx` field must be passed back to the caller via the context. + 2. `read_post_processing` stores the temporary value in the context. + 3. `until_with_ctx` in the surrounding array uses this context to control how much data to read (until the `fx` flag is zero). 4. **During writing**: - The index is updated in each step and the `auto_fx` field is set accordingly (last entry is set to `false`). @@ -1203,7 +1203,7 @@ Example: * The user (providing the x/y data) is not aware of the index and the `fx` field stored on disk (and cannot break the binary data format by providing some wrong data). -* Note: this enables to transparently implement "Extended Length Data Items" from the +* Note: this enables to implement "Extended Length Data Items" from the [ASTERIX EUROCONTOL](https://www.eurocontrol.int/publication/eurocontrol-specification-surveillance-data-exchange-part-i) standard, without making control flags (`fx`) visible in the user data. @@ -1214,15 +1214,17 @@ Example: # fn main() { #[derive(Debug, Clone)] struct IndexContext { - idx: std::rc::Rc>, - n: usize, - fx: std::rc::Rc>, + idx: std::rc::Rc>, // see text, 1.1 + n: usize, // see text, 1.2 + fx: std::rc::Rc>, // see text, 1.3 } #[deku_derive(DekuRead, DekuWrite)] #[derive(PartialEq, Debug, Clone)] struct A { #[deku( - until_with_ctx = "|_:&B,ctx:IndexContext| !ctx.fx.get()", + until_with_ctx = "|_:&B,ctx:IndexContext| !ctx.fx.get()", // see text, 3.1 + 3.3 + + // see text, 2 ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: 0, fx: std::rc::Rc::new(std::cell::Cell::new(false))}", writer_ctx = "IndexContext { idx: std::rc::Rc::new(std::cell::Cell::new(0)), n: items.len(), fx: std::rc::Rc::new(std::cell::Cell::new(false)) }" )] @@ -1243,9 +1245,9 @@ struct B { )] idx_automatically_filled: u8, #[deku( - read_post_processing = "ctx.fx.set(*auto_fx!=0);", + read_post_processing = "ctx.fx.set(*auto_fx!=0);", // see text, 3.2 + 3.3 temp, - temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" + temp_value = "if ctx.idx.get() < ctx.n {1} else {0}" // see text, 4 )] auto_fx: u8, }