Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement serde for all the containers #60

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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"]
37 changes: 37 additions & 0 deletions src/index_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<K, V, S = RandomState> {
Expand Down Expand Up @@ -314,3 +317,37 @@ impl<T: Hash + Eq, S: PartialEq> PartialEq for FrozenIndexMap<T, S> {
ret
}
}

#[cfg(feature = "serde")]
impl<K, V, S> Serialize for FrozenIndexMap<K, V, S>
where
K: Serialize + Eq + Hash,
V: Serialize,
S: BuildHasher,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
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<K, V, S>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
S: BuildHasher + Default,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
IndexMap::deserialize(deserializer).map(FrozenIndexMap::from)
}
}
35 changes: 35 additions & 0 deletions src/index_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, S = RandomState> {
Expand Down Expand Up @@ -275,3 +278,35 @@ impl<T: Hash + Eq, S: BuildHasher> PartialEq for FrozenIndexSet<T, S> {
ret
}
}

#[cfg(feature = "serde")]
impl<T, S> Serialize for FrozenIndexSet<T, S>
where
T: Eq + Hash + Serialize,
S: BuildHasher,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
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<K, S>
where
K: Deserialize<'de> + Eq + Hash,
S: BuildHasher + Default,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
IndexSet::deserialize(deserializer).map(FrozenIndexSet::from)
}
}
69 changes: 69 additions & 0 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<K, V, S = RandomState> {
Expand Down Expand Up @@ -283,6 +286,40 @@ impl<K: Clone, V: Clone, S: Clone> Clone for FrozenMap<K, V, S> {
}
}

#[cfg(feature = "serde")]
impl<K, V, S> Serialize for FrozenMap<K, V, S>
where
K: Serialize + Eq + Hash,
V: Serialize,
S: BuildHasher,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
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<K, V, S>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
S: BuildHasher + Default,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<K, V> {
Expand Down Expand Up @@ -534,3 +571,35 @@ impl<K: Eq + Hash, V: PartialEq + StableDeref> PartialEq for FrozenMap<K, V> {
ret
}
}

#[cfg(feature = "serde")]
impl<K, V> Serialize for FrozenBTreeMap<K, V>
where
K: Serialize + Eq + Hash,
V: Serialize,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
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<K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
BTreeMap::deserialize(deserializer).map(FrozenBTreeMap::from)
}
}
118 changes: 118 additions & 0 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<K, V> {
map: RwLock<HashMap<K, V>>,
}
Expand Down Expand Up @@ -431,8 +439,24 @@ impl<K: Eq + Hash, V: PartialEq> PartialEq for FrozenMap<K, V> {
}
}

#[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::<FrozenMap<String, String>>(&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<T> {
vec: RwLock<Vec<T>>,
}
Expand Down Expand Up @@ -623,6 +647,20 @@ impl<T: PartialEq> PartialEq for FrozenVec<T> {
}
}

#[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::<FrozenVec<String>>(&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
Expand Down Expand Up @@ -924,9 +962,52 @@ impl<T: Copy + Clone> Clone for LockFreeFrozenVec<T> {
}
}

#[cfg(feature = "serde")]
impl<T: Copy + Serialize> Serialize for LockFreeFrozenVec<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::{SeqAccess, Visitor};
use std::marker::PhantomData;

struct SeqVisitor<T>(PhantomData<T>);

impl<'de, T: Copy + Deserialize<'de>> Visitor<'de> for SeqVisitor<T> {
type Value = LockFreeFrozenVec<T>;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a sequence of elements")
}

fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
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();
Expand Down Expand Up @@ -982,13 +1063,35 @@ 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::<LockFreeFrozenVec<Moo>>(&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::<LockFreeFrozenVec<Moo>>(&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

/// 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<K, V>(RwLock<BTreeMap<K, V>>);

impl<K: Clone + Ord, V: StableDeref> FrozenBTreeMap<K, V> {
Expand Down Expand Up @@ -1183,3 +1286,18 @@ impl<K: PartialEq, V: PartialEq> PartialEq for FrozenBTreeMap<K, V> {
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::<FrozenBTreeMap<String, String>>(&map_json).unwrap();
assert_eq!(map, map_serde);
}
}
Loading