Skip to content
Merged
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ script:
- cargo run --example arena
- cargo run --example mutable_arena
- cargo run --example sync
- cargo run --example string_interner --features indexmap

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,12 @@ categories = ["data-structures", "caching"]

[dependencies]
stable_deref_trait = "1.1.1"
indexmap = { version = "1.6", optional = true }

[package.metadata.docs.rs]
features = ["indexmap"]

[[example]]
name = "string_interner"
path = "examples/string_interner.rs"
required-features = ["indexmap"]
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ This crate provides various "frozen" collections.
These are append-only collections where references to entries can be held on to even across insertions. This is safe because these collections only support storing data that's present behind some indirection -- i.e. `String`, `Vec<T>`, `Box<T>`, etc, and they only yield references to the data behind the allocation (`&str`, `&[T]`, and `&T` respectively)

The typical use case is having a global cache of strings or other data which the rest of the program borrows from.

### Running all examples

```bash
cargo test --examples --features indexmap
```
61 changes: 61 additions & 0 deletions examples/string_interner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::convert::AsRef;
use std::collections::BTreeSet;

use elsa::FrozenIndexSet;

struct StringInterner {
set: FrozenIndexSet<String>
}

impl StringInterner {
fn new() -> Self {
StringInterner {
set: FrozenIndexSet::new()
}
}

fn get_or_intern<T>(&self, value: T) -> usize
where
T: AsRef<str>
{
// TODO use Entry in case the standard Entry API gets improved
// (here to avoid premature allocation or double lookup)
self.set.insert_full(value.as_ref().to_string()).0
}

fn get<T>(&self, value: T) -> Option<usize>
where
T: AsRef<str>
{
self.set.get_full(value.as_ref()).map(|(i, _r)| i)
}

fn resolve(&self, index: usize) -> Option<&str> {
self.set.get_index(index)
}
}

fn main() {
let interner = StringInterner::new();
let lonely = interner.get_or_intern("lonely");
let best_friend = interner.get_or_intern("best friend");
let threes_a_crowd = interner.get_or_intern("threes a crowd");
let rando = interner.get_or_intern("rando");
let _facebook = interner.get_or_intern("facebook");

let best_friend_2 = interner.get_or_intern("best friend");
let best_friend_3 = interner.get("best friend").unwrap();

let best_friend_ref = interner.resolve(best_friend).unwrap();

let mut set = BTreeSet::new();
set.insert(lonely);
set.insert(best_friend);
set.insert(threes_a_crowd);
set.insert(rando);
set.insert(best_friend_2);
assert_eq!(set.len(), 4);
assert_eq!(best_friend, best_friend_2);
assert_eq!(best_friend_2, best_friend_3);
assert_eq!(best_friend_ref, "best friend");
}
133 changes: 133 additions & 0 deletions src/index_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use std::borrow::Borrow;
use std::cell::{Cell, UnsafeCell};
use std::hash::Hash;
use std::iter::FromIterator;
use std::ops::Index;

use indexmap::IndexMap;
use stable_deref_trait::StableDeref;

/// Append-only version of `indexmap::IndexMap` where
/// insertion does not require mutable access
pub struct FrozenIndexMap<K, V> {
map: UnsafeCell<IndexMap<K, V>>,
/// Eq/Hash implementations can have side-effects, and using Rc it is possible
/// for FrozenIndexMap::insert to be called on a key that itself contains the same
/// `FrozenIndexMap`, whose `eq` implementation also calls FrozenIndexMap::insert
///
/// We use this `in_use` flag to guard against any reentrancy.
in_use: Cell<bool>,
}

// safety: UnsafeCell implies !Sync

impl<K: Eq + Hash, V> FrozenIndexMap<K, V> {
pub fn new() -> Self {
Self {
map: UnsafeCell::new(Default::default()),
in_use: Cell::new(false)
}
}
}

impl<K: Eq + Hash, V: StableDeref> FrozenIndexMap<K, V> {
// these should never return &K or &V
// these should never delete any entries
pub fn insert(&self, k: K, v: V) -> &V::Target {
assert!(!self.in_use.get());
self.in_use.set(true);
let ret = unsafe {
let map = self.map.get();
&*(*map).entry(k).or_insert(v)
};
self.in_use.set(false);
ret
}

// these should never return &K or &V
// these should never delete any entries
pub fn insert_full(&self, k: K, v: V) -> (usize, &V::Target) {
assert!(!self.in_use.get());
self.in_use.set(true);
let ret = unsafe {
let map = self.map.get();
let entry = (*map).entry(k);
let index = entry.index();
(index, &**entry.or_insert(v))
};
self.in_use.set(false);
ret
}

pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V::Target>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
assert!(!self.in_use.get());
self.in_use.set(true);
let ret = unsafe {
let map = self.map.get();
(*map).get(k).map(|x| &**x)
};
self.in_use.set(false);
ret
}

pub fn into_map(self) -> IndexMap<K, V> {
self.map.into_inner()
}

/// Get mutable access to the underlying [`IndexMap`].
///
/// This is safe, as it requires a `&mut self`, ensuring nothing is using
/// the 'frozen' contents.
pub fn as_mut(&mut self) -> &mut IndexMap<K, V> {
unsafe {
&mut *self.map.get()
}
}

/// Returns true if the map contains no elements.
pub fn is_empty(&self) -> bool {
assert!(!self.in_use.get());
self.in_use.set(true);
let ret = unsafe {
let map = self.map.get();
(*map).is_empty()
};
self.in_use.set(false);
ret
}
}

impl<K, V> From<IndexMap<K, V>> for FrozenIndexMap<K, V> {
fn from(map: IndexMap<K, V>) -> Self {
Self {
map: UnsafeCell::new(map),
in_use: Cell::new(false)
}
}
}

impl<K: Eq + Hash, V: StableDeref> Index<K> for FrozenIndexMap<K, V> {
type Output = V::Target;
fn index(&self, idx: K) -> &V::Target {
self.get(&idx).expect("attempted to index FrozenIndexMap with unknown key")
}
}

impl<K: Eq + Hash, V> FromIterator<(K, V)> for FrozenIndexMap<K, V> {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (K, V)> {
let map: IndexMap<_, _> = iter.into_iter().collect();
map.into()
}
}

impl<K: Eq + Hash, V:> Default for FrozenIndexMap<K, V> {
fn default() -> Self {
FrozenIndexMap::new()
}
}
Loading