diff --git a/Cargo.toml b/Cargo.toml index bb55cfc..670d19b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ categories = ["data-structures", "caching"] [dependencies] stable_deref_trait = "1.1.1" +serde = { version = "1.0.171", features = ["derive"], optional = true } indexmap = { version = "2.0.2", optional = true } [package.metadata.docs.rs] @@ -22,6 +23,10 @@ name = "string_interner" path = "examples/string_interner.rs" required-features = ["indexmap"] +[dev-dependencies] +serde_json = "1.0.104" + [features] default = [] +serde = ["dep:serde", "indexmap/serde"] indexmap = ["dep:indexmap"] diff --git a/src/index_map.rs b/src/index_map.rs index 4a23352..820a43a 100644 --- a/src/index_map.rs +++ b/src/index_map.rs @@ -8,6 +8,9 @@ use std::ops::Index; use indexmap::IndexMap; use stable_deref_trait::StableDeref; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + /// Append-only version of `indexmap::IndexMap` where /// insertion does not require mutable access pub struct FrozenIndexMap { @@ -314,3 +317,37 @@ impl PartialEq for FrozenIndexMap { ret } } + +#[cfg(feature = "serde")] +impl Serialize for FrozenIndexMap +where + K: Serialize + Eq + Hash, + V: Serialize, + S: BuildHasher, +{ + fn serialize(&self, serializer: Ser) -> Result + where + Ser: Serializer, + { + assert!(!self.in_use.get()); + self.in_use.set(true); + let map_serialized = unsafe { self.map.get().as_ref().unwrap() }.serialize(serializer); + self.in_use.set(false); + return map_serialized; + } +} + +#[cfg(feature = "serde")] +impl<'de, K, V, S> Deserialize<'de> for FrozenIndexMap +where + K: Deserialize<'de> + Eq + Hash, + V: Deserialize<'de>, + S: BuildHasher + Default, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + IndexMap::deserialize(deserializer).map(FrozenIndexMap::from) + } +} diff --git a/src/index_set.rs b/src/index_set.rs index 2391327..199188e 100644 --- a/src/index_set.rs +++ b/src/index_set.rs @@ -8,6 +8,9 @@ use std::ops::Index; use indexmap::IndexSet; use stable_deref_trait::StableDeref; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + /// Append-only version of `indexmap::IndexSet` where /// insertion does not require mutable access pub struct FrozenIndexSet { @@ -275,3 +278,35 @@ impl PartialEq for FrozenIndexSet { ret } } + +#[cfg(feature = "serde")] +impl Serialize for FrozenIndexSet +where + T: Eq + Hash + Serialize, + S: BuildHasher, +{ + fn serialize(&self, serializer: Ser) -> Result + where + Ser: Serializer, + { + assert!(!self.in_use.get()); + self.in_use.set(true); + let map_serialized = unsafe { self.set.get().as_ref().unwrap() }.serialize(serializer); + self.in_use.set(false); + return map_serialized; + } +} + +#[cfg(feature = "serde")] +impl<'de, K, S> Deserialize<'de> for FrozenIndexSet +where + K: Deserialize<'de> + Eq + Hash, + S: BuildHasher + Default, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + IndexSet::deserialize(deserializer).map(FrozenIndexSet::from) + } +} diff --git a/src/map.rs b/src/map.rs index befa3e6..0a485b8 100644 --- a/src/map.rs +++ b/src/map.rs @@ -9,6 +9,9 @@ use std::ops::Index; use stable_deref_trait::StableDeref; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + /// Append-only version of `std::collections::HashMap` where /// insertion does not require mutable access pub struct FrozenMap { @@ -283,6 +286,40 @@ impl Clone for FrozenMap { } } +#[cfg(feature = "serde")] +impl Serialize for FrozenMap +where + K: Serialize + Eq + Hash, + V: Serialize, + S: BuildHasher, +{ + fn serialize(&self, serializer: Ser) -> Result + where + Ser: Serializer, + { + assert!(!self.in_use.get()); + self.in_use.set(true); + let map_serialized = unsafe { self.map.get().as_ref().unwrap() }.serialize(serializer); + self.in_use.set(false); + return map_serialized; + } +} + +#[cfg(feature = "serde")] +impl<'de, K, V, S> Deserialize<'de> for FrozenMap +where + K: Deserialize<'de> + Eq + Hash, + V: Deserialize<'de>, + S: BuildHasher + Default, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + HashMap::deserialize(deserializer).map(FrozenMap::from) + } +} + /// Append-only version of `std::collections::BTreeMap` where /// insertion does not require mutable access pub struct FrozenBTreeMap { @@ -534,3 +571,35 @@ impl PartialEq for FrozenMap { ret } } + +#[cfg(feature = "serde")] +impl Serialize for FrozenBTreeMap +where + K: Serialize + Eq + Hash, + V: Serialize, +{ + fn serialize(&self, serializer: Ser) -> Result + where + Ser: Serializer, + { + assert!(!self.in_use.get()); + self.in_use.set(true); + let map_serialized = unsafe { self.map.get().as_ref().unwrap() }.serialize(serializer); + self.in_use.set(false); + return map_serialized; + } +} + +#[cfg(feature = "serde")] +impl<'de, K: Clone + Ord, V: StableDeref> Deserialize<'de> for FrozenBTreeMap +where + K: Deserialize<'de> + Eq + Hash, + V: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + BTreeMap::deserialize(deserializer).map(FrozenBTreeMap::from) + } +} diff --git a/src/sync.rs b/src/sync.rs index bb9e608..804d428 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -25,8 +25,16 @@ use std::sync::atomic::Ordering; use std::sync::RwLock; use std::sync::TryLockError; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + /// Append-only threadsafe version of `std::collections::HashMap` where /// insertion does not require mutable access +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(bound(deserialize = "K: Eq + Hash + Deserialize<'de>, V: Deserialize<'de>")) +)] pub struct FrozenMap { map: RwLock>, } @@ -431,8 +439,24 @@ impl PartialEq for FrozenMap { } } +#[test] +fn test_sync_frozen_map() { + #[cfg(feature = "serde")] + { + let map = FrozenMap::new(); + map.insert(String::from("a"), String::from("b")); + + let map_json = serde_json::to_string(&map).unwrap(); + assert_eq!(map_json, "{\"map\":{\"a\":\"b\"}}"); + + let map_serde = serde_json::from_str::>(&map_json).unwrap(); + assert_eq!(map, map_serde); + } +} + /// Append-only threadsafe version of `std::vec::Vec` where /// insertion does not require mutable access +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct FrozenVec { vec: RwLock>, } @@ -623,6 +647,20 @@ impl PartialEq for FrozenVec { } } +#[test] +fn test_sync_frozen_vec() { + #[cfg(feature = "serde")] + { + let vec = FrozenVec::new(); + vec.push(String::from("a")); + + let vec_json = serde_json::to_string(&vec).unwrap(); + assert_eq!(vec_json, "{\"vec\":[\"a\"]}"); + let vec_serde = serde_json::from_str::>(&vec_json).unwrap(); + assert_eq!(vec, vec_serde); + } +} + // The context for these functions is that we want to have a // series of exponentially increasing buffer sizes. We want // to maximize the total size of the buffers (since this @@ -924,9 +962,52 @@ impl Clone for LockFreeFrozenVec { } } +#[cfg(feature = "serde")] +impl Serialize for LockFreeFrozenVec { + fn serialize(&self, serializer: S) -> Result { + use serde::ser::SerializeSeq; + + let len = self.len.load(Ordering::Relaxed); + let mut seq = serializer.serialize_seq(Some(len))?; + for i in 0..len { + seq.serialize_element(&self.get(i).unwrap())?; + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de, T: Copy + Deserialize<'de>> Deserialize<'de> for LockFreeFrozenVec { + fn deserialize>(deserializer: D) -> Result { + use serde::de::{SeqAccess, Visitor}; + use std::marker::PhantomData; + + struct SeqVisitor(PhantomData); + + impl<'de, T: Copy + Deserialize<'de>> Visitor<'de> for SeqVisitor { + type Value = LockFreeFrozenVec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence of elements") + } + + fn visit_seq>(self, mut seq: A) -> Result { + let ret = LockFreeFrozenVec::new(); + while let Some(elem) = seq.next_element()? { + ret.push(elem); + } + Ok(ret) + } + } + + deserializer.deserialize_seq(SeqVisitor(PhantomData)) + } +} + #[test] fn test_non_lockfree() { #[derive(Copy, Clone, Debug, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] struct Moo(i32); let vec = LockFreeFrozenVec::new(); @@ -982,6 +1063,23 @@ fn test_non_lockfree() { // Test dropping empty vecs LockFreeFrozenVec::<()>::new(); + + #[cfg(feature = "serde")] + { + let vec = LockFreeFrozenVec::new(); + let json_empty = serde_json::to_string(&vec).unwrap(); + let vec_empty = serde_json::from_str::>(&json_empty).unwrap(); + assert_eq!(vec_empty.get(0), None); + + vec.push(Moo(1)); + vec.push(Moo(2)); + vec.push(Moo(3)); + let json = serde_json::to_string(&vec).unwrap(); + let serde_vec = serde_json::from_str::>(&json).unwrap(); + assert_eq!(serde_vec.get(0), Some(Moo(1))); + assert_eq!(serde_vec.get(1), Some(Moo(2))); + assert_eq!(serde_vec.get(2), Some(Moo(3))); + } } // TODO: Implement IntoIterator for LockFreeFrozenVec @@ -989,6 +1087,11 @@ fn test_non_lockfree() { /// Append-only threadsafe version of `std::collections::BTreeMap` where /// insertion does not require mutable access #[derive(Debug)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(bound(deserialize = "K: Ord + Deserialize<'de>, V: Deserialize<'de>")) +)] pub struct FrozenBTreeMap(RwLock>); impl FrozenBTreeMap { @@ -1183,3 +1286,18 @@ impl PartialEq for FrozenBTreeMap { self_ref == other_ref } } + +#[test] +fn test_sync_frozen_btreemap() { + #[cfg(feature = "serde")] + { + let map = FrozenBTreeMap::new(); + map.insert(String::from("a"), String::from("b")); + + let map_json = serde_json::to_string(&map).unwrap(); + assert_eq!(map_json, "{\"a\":\"b\"}"); + + let map_serde = serde_json::from_str::>(&map_json).unwrap(); + assert_eq!(map, map_serde); + } +}