From de48e3b07edce472600ab97a90f892148a541c41 Mon Sep 17 00:00:00 2001 From: Asempere Date: Thu, 24 Jul 2025 16:37:35 +0200 Subject: [PATCH 1/2] feat: add serde support --- enum-collections/Cargo.toml | 7 ++- enum-collections/src/enummap.rs | 49 +++++++++++++++++++- enum-collections/src/lib.rs | 2 + enum-collections/src/serde.rs | 81 +++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 enum-collections/src/serde.rs diff --git a/enum-collections/Cargo.toml b/enum-collections/Cargo.toml index 74696b0..1669401 100644 --- a/enum-collections/Cargo.toml +++ b/enum-collections/Cargo.toml @@ -21,13 +21,18 @@ eq = [] # Generates a static array of all variants for the EnumMap. This is required for the `Debug` implementation. variants = ["enum-collections-macros/variants"] # Implementations of the `Enumerated` trait for common data types. -ext=[] +ext = [] +# Serde's `Serialize` and `Deserialize` trait implementations +serde = ["variants", "dep:serde"] [dependencies] enum-collections-macros = { path = "../enum-collections-macros", version = "1.0.1" } +serde = { version = "1.0", optional = true } [dev-dependencies] criterion = "0.5.1" +ron = "0.10.1" +serde = { version = "1.0", features = ["derive"] } [[bench]] name = "enummap" diff --git a/enum-collections/src/enummap.rs b/enum-collections/src/enummap.rs index a5c6749..994c586 100644 --- a/enum-collections/src/enummap.rs +++ b/enum-collections/src/enummap.rs @@ -409,7 +409,7 @@ impl EnumMap { /// /// ``` #[cfg(feature = "variants")] - pub fn new_inspect(default: fn(&K) -> V) -> Self { + pub fn new_inspect(mut default: impl FnMut(&K) -> V) -> Self { let init_fn = |index| { // Finds the enum variant by its index, as the array is sorted by discriminants in ascending order. default(&K::VARIANTS[index]) @@ -691,4 +691,51 @@ mod tests { assert_eq!("{A: 42, B: 42}", format!("{:?}", enum_map)); } } + + #[cfg(feature = "serde")] + mod serde { + use serde::{Deserialize, Serialize}; + + use crate::{EnumMap, Enumerated}; + + #[derive(Enumerated, Serialize, Deserialize, PartialEq, Debug)] + pub(super) enum LetterSerde { + A, + B, + } + + #[test] + fn serialize() { + let mut enum_map: EnumMap<_, Option<_>, { LetterSerde::SIZE }> = EnumMap::new_option(); + enum_map[LetterSerde::A] = Some(10); + enum_map[LetterSerde::B] = Some(11); + + let serialized = ron::to_string(&enum_map).unwrap(); + assert_eq!("{A:Some(10),B:Some(11)}", serialized); + } + + #[test] + fn deserialize() { + let str = "{A:Some(10),B:Some(11)}"; + let enum_map: EnumMap<_, Option<_>, { LetterSerde::SIZE }> = + ron::from_str(str).unwrap(); + + let mut correct_enum_map: EnumMap<_, Option<_>, { LetterSerde::SIZE }> = + EnumMap::new_option(); + correct_enum_map[LetterSerde::A] = Some(10); + correct_enum_map[LetterSerde::B] = Some(11); + assert_eq!(enum_map, correct_enum_map); + } + + #[test] + fn serde() { + let mut enum_map: EnumMap<_, Option<_>, { LetterSerde::SIZE }> = EnumMap::new_option(); + enum_map[LetterSerde::A] = Some(10); + + let serialized = ron::to_string(&enum_map).unwrap(); + let new_enum_map: EnumMap<_, Option<_>, { LetterSerde::SIZE }> = + ron::from_str(&serialized).unwrap(); + assert_eq!(enum_map, new_enum_map); + } + } } diff --git a/enum-collections/src/lib.rs b/enum-collections/src/lib.rs index 78089ef..5f1a915 100644 --- a/enum-collections/src/lib.rs +++ b/enum-collections/src/lib.rs @@ -19,6 +19,8 @@ mod enumerated; mod enummap; #[cfg(feature = "ext")] mod ext; +#[cfg(feature = "serde")] +mod serde; pub use crate::enumerated::Enumerated; pub use crate::enummap::EnumMap; diff --git a/enum-collections/src/serde.rs b/enum-collections/src/serde.rs new file mode 100644 index 0000000..dd21f5f --- /dev/null +++ b/enum-collections/src/serde.rs @@ -0,0 +1,81 @@ +use serde::{de::Visitor, Deserialize, Serialize}; +use std::{any::type_name, marker::PhantomData}; + +use crate::{EnumMap, Enumerated}; + +impl Serialize for EnumMap { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.collect_map(self.iter_kv()) + } +} + +struct EnumMapVisitor< + 'de, + K: Enumerated + Deserialize<'de> + PartialEq, + V: Deserialize<'de> + Default, + const N: usize, +> { + marker: PhantomData<&'de (K, V)>, +} + +impl< + 'de, + K: Enumerated + Deserialize<'de> + PartialEq, + V: Deserialize<'de> + Default, + const N: usize, + > Visitor<'de> for EnumMapVisitor<'de, K, V, N> +{ + type Value = EnumMap; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + formatter, + "EnumMap<{}, {}>", + type_name::(), + type_name::() + ) + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + // Used a Vec instead of a map since an enum will never have so many variants that the hashing cost + code bloat is worth + let mut kv_vec = Vec::new(); + while let Some((k, v)) = map.next_entry::()? { + kv_vec.push((k, v)); + } + + Ok(EnumMap::new_inspect(move |k| { + let idx = kv_vec.iter().position(|(curr_key, _v)| curr_key == k); + if let Some(idx) = idx { + let (_k, v) = kv_vec.swap_remove(idx); + v + } else { + V::default() + } + })) + } +} + +impl< + 'de, + K: Enumerated + Deserialize<'de> + PartialEq, + V: Deserialize<'de> + Default + 'de, + const N: usize, + > Deserialize<'de> for EnumMap +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let visitor = EnumMapVisitor { + marker: PhantomData, + }; + deserializer.deserialize_map(visitor) + } +} From cfdad6a575e78d2655637c756ba28771bf3b4b67 Mon Sep 17 00:00:00 2001 From: Asempere Date: Fri, 25 Jul 2025 13:51:19 +0200 Subject: [PATCH 2/2] Made it not use heap --- enum-collections/src/enummap.rs | 4 ++-- enum-collections/src/serde.rs | 19 +++++++------------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/enum-collections/src/enummap.rs b/enum-collections/src/enummap.rs index 994c586..f4f0ac8 100644 --- a/enum-collections/src/enummap.rs +++ b/enum-collections/src/enummap.rs @@ -199,8 +199,8 @@ mod macro_test { /// /// ``` pub struct EnumMap { - data: [V; N], - _key: PhantomData, + pub(crate) data: [V; N], + pub(crate) _key: PhantomData, } impl EnumMap { diff --git a/enum-collections/src/serde.rs b/enum-collections/src/serde.rs index dd21f5f..d9b41aa 100644 --- a/enum-collections/src/serde.rs +++ b/enum-collections/src/serde.rs @@ -44,21 +44,15 @@ impl< where A: serde::de::MapAccess<'de>, { - // Used a Vec instead of a map since an enum will never have so many variants that the hashing cost + code bloat is worth - let mut kv_vec = Vec::new(); + let mut data = std::array::from_fn(|_| V::default()); while let Some((k, v)) = map.next_entry::()? { - kv_vec.push((k, v)); + data[k.position()] = v; } - Ok(EnumMap::new_inspect(move |k| { - let idx = kv_vec.iter().position(|(curr_key, _v)| curr_key == k); - if let Some(idx) = idx { - let (_k, v) = kv_vec.swap_remove(idx); - v - } else { - V::default() - } - })) + Ok(EnumMap { + data, + _key: PhantomData, + }) } } @@ -69,6 +63,7 @@ impl< const N: usize, > Deserialize<'de> for EnumMap { + #[inline] fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>,