diff --git a/benches/bench.rs b/benches/bench.rs index b00a1c0..238caed 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -39,7 +39,7 @@ criterion_main!(bench_get_or_intern, bench_resolve, bench_get, bench_iter); fn bench_get_or_intern_static(c: &mut Criterion) { let mut g = c.benchmark_group("get_or_intern_static"); - fn bench_for_backend(g: &mut BenchmarkGroup) { + fn bench_for_backend<'i, BB: BackendBenchmark<'i>>(g: &mut BenchmarkGroup) { #[rustfmt::skip] let static_strings = &[ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", @@ -87,7 +87,7 @@ fn bench_get_or_intern_static(c: &mut Criterion) { fn bench_get_or_intern_fill_with_capacity(c: &mut Criterion) { let mut g = c.benchmark_group("get_or_intern/fill-empty/with_capacity"); g.throughput(Throughput::Elements(BENCH_LEN_STRINGS as u64)); - fn bench_for_backend(g: &mut BenchmarkGroup) { + fn bench_for_backend<'i, BB: BackendBenchmark<'i>>(g: &mut BenchmarkGroup) { g.bench_with_input( BB::NAME, &(BENCH_LEN_STRINGS, BENCH_STRING_LEN), @@ -113,7 +113,7 @@ fn bench_get_or_intern_fill_with_capacity(c: &mut Criterion) { fn bench_get_or_intern_fill(c: &mut Criterion) { let mut g = c.benchmark_group("get_or_intern/fill-empty/new"); g.throughput(Throughput::Elements(BENCH_LEN_STRINGS as u64)); - fn bench_for_backend(g: &mut BenchmarkGroup) { + fn bench_for_backend<'i, BB: BackendBenchmark<'i>>(g: &mut BenchmarkGroup) { g.bench_with_input( BB::NAME, &(BENCH_LEN_STRINGS, BENCH_STRING_LEN), @@ -139,7 +139,7 @@ fn bench_get_or_intern_fill(c: &mut Criterion) { fn bench_get_or_intern_already_filled(c: &mut Criterion) { let mut g = c.benchmark_group("get_or_intern/already-filled"); g.throughput(Throughput::Elements(BENCH_LEN_STRINGS as u64)); - fn bench_for_backend(g: &mut BenchmarkGroup) { + fn bench_for_backend<'i, BB: BackendBenchmark<'i>>(g: &mut BenchmarkGroup) { g.bench_with_input( BB::NAME, &(BENCH_LEN_STRINGS, BENCH_STRING_LEN), @@ -165,7 +165,7 @@ fn bench_get_or_intern_already_filled(c: &mut Criterion) { fn bench_resolve_already_filled(c: &mut Criterion) { let mut g = c.benchmark_group("resolve/already-filled"); g.throughput(Throughput::Elements(BENCH_LEN_STRINGS as u64)); - fn bench_for_backend(g: &mut BenchmarkGroup) { + fn bench_for_backend<'i, BB: BackendBenchmark<'i>>(g: &mut BenchmarkGroup) { g.bench_with_input( BB::NAME, &(BENCH_LEN_STRINGS, BENCH_STRING_LEN), @@ -191,7 +191,7 @@ fn bench_resolve_already_filled(c: &mut Criterion) { fn bench_resolve_unchecked_already_filled(c: &mut Criterion) { let mut g = c.benchmark_group("resolve_unchecked/already-filled"); g.throughput(Throughput::Elements(BENCH_LEN_STRINGS as u64)); - fn bench_for_backend(g: &mut BenchmarkGroup) { + fn bench_for_backend<'i, BB: BackendBenchmark<'i>>(g: &mut BenchmarkGroup) { g.bench_with_input( BB::NAME, &(BENCH_LEN_STRINGS, BENCH_STRING_LEN), @@ -220,7 +220,7 @@ fn bench_resolve_unchecked_already_filled(c: &mut Criterion) { fn bench_get_already_filled(c: &mut Criterion) { let mut g = c.benchmark_group("get/already-filled"); g.throughput(Throughput::Elements(BENCH_LEN_STRINGS as u64)); - fn bench_for_backend(g: &mut BenchmarkGroup) { + fn bench_for_backend<'i, BB: BackendBenchmark<'i>>(g: &mut BenchmarkGroup) { g.bench_with_input( BB::NAME, &(BENCH_LEN_STRINGS, BENCH_STRING_LEN), @@ -246,11 +246,11 @@ fn bench_get_already_filled(c: &mut Criterion) { fn bench_iter_already_filled(c: &mut Criterion) { let mut g = c.benchmark_group("iter/already-filled"); g.throughput(Throughput::Elements(BENCH_LEN_STRINGS as u64)); - fn bench_for_backend(g: &mut BenchmarkGroup) + fn bench_for_backend<'i, BB: BackendBenchmark<'i>>(g: &mut BenchmarkGroup) where - for<'a> &'a ::Backend: IntoIterator< + for<'a> &'a >::Backend: IntoIterator< Item = ( - <::Backend as Backend>::Symbol, + <>::Backend as Backend<'i>>::Symbol, &'a str, ), >, diff --git a/benches/setup.rs b/benches/setup.rs index cb8adcc..5428692 100644 --- a/benches/setup.rs +++ b/benches/setup.rs @@ -1,7 +1,6 @@ use string_interner::{ backend::{Backend, BucketBackend, BufferBackend, StringBackend}, - DefaultSymbol, - StringInterner, + DefaultSymbol, StringInterner, }; /// Alphabet containing all characters that may be put into a benchmark string. @@ -79,53 +78,64 @@ pub const BENCH_LEN_STRINGS: usize = 100_000; pub const BENCH_STRING_LEN: usize = 5; type FxBuildHasher = fxhash::FxBuildHasher; -type StringInternerWith = StringInterner; +type StringInternerWith<'i, B> = StringInterner<'i, B, FxBuildHasher>; -pub trait BackendBenchmark { +pub trait BackendBenchmark<'i> { const NAME: &'static str; - type Backend: Backend; + type Backend: Backend<'i>; - fn setup() -> StringInternerWith { + fn setup() -> StringInternerWith<'i, Self::Backend> { >::new() } - fn setup_with_capacity(cap: usize) -> StringInternerWith { + fn setup_with_capacity(cap: usize) -> StringInternerWith<'i, Self::Backend> { >::with_capacity(cap) } - fn setup_filled(words: &[String]) -> StringInternerWith { - words.iter().collect::>() + fn setup_filled(words: I) -> StringInternerWith<'i, Self::Backend> + where + I: IntoIterator, + S: AsRef, + { + words + .into_iter() + .map(|it| it.as_ref().to_string()) + .collect::>() } - fn setup_filled_with_ids( - words: &[String], + fn setup_filled_with_ids( + words: I, ) -> ( - StringInternerWith, - Vec<::Symbol>, - ) { - let mut interner = >::new(); + StringInternerWith<'i, Self::Backend>, + Vec<>::Symbol>, + ) + where + I: IntoIterator, + S: AsRef, + { + let mut interner = >::new(); let word_ids = words - .iter() - .map(|word| interner.get_or_intern(word)) + .into_iter() + .map(|word| interner.get_or_intern(word.as_ref())) .collect::>(); (interner, word_ids) } } pub struct BenchBucket; -impl BackendBenchmark for BenchBucket { +impl<'i> BackendBenchmark<'i> for BenchBucket { const NAME: &'static str = "BucketBackend"; - type Backend = BucketBackend; + type Backend = BucketBackend<'i, DefaultSymbol>; } pub struct BenchString; -impl BackendBenchmark for BenchString { +impl<'i> BackendBenchmark<'i> for BenchString { const NAME: &'static str = "StringBackend"; - type Backend = StringBackend; + type Backend = StringBackend<'i, DefaultSymbol>; } pub struct BenchBuffer; -impl BackendBenchmark for BenchBuffer { +impl<'i> BackendBenchmark<'i> for BenchBuffer { const NAME: &'static str = "BufferBackend"; - type Backend = BufferBackend; + type Backend = BufferBackend<'i, DefaultSymbol>; } diff --git a/src/backend/bucket/mod.rs b/src/backend/bucket/mod.rs index 5e5ad48..4ecf2b4 100644 --- a/src/backend/bucket/mod.rs +++ b/src/backend/bucket/mod.rs @@ -4,50 +4,44 @@ mod fixed_str; mod interned_str; use self::{fixed_str::FixedString, interned_str::InternedStr}; -use super::Backend; +use super::{Backend, PhantomBackend}; use crate::{symbol::expect_valid_symbol, DefaultSymbol, Symbol}; use alloc::{string::String, vec::Vec}; use core::{iter::Enumerate, marker::PhantomData, slice}; -/// An interner backend that reduces memory allocations by using string buckets. -/// -/// # Note -/// -/// Implementation inspired by matklad's blog post that can be found here: -/// -/// -/// # Usage Hint -/// -/// Use when deallocations or copy overhead is costly or when -/// interning of static strings is especially common. -/// -/// # Usage -/// -/// - **Fill:** Efficiency of filling an empty string interner. -/// - **Resolve:** Efficiency of interned string look-up given a symbol. -/// - **Allocations:** The number of allocations performed by the backend. -/// - **Footprint:** The total heap memory consumed by the backend. -/// - **Contiguous:** True if the returned symbols have contiguous values. -/// - **Iteration:** Efficiency of iterating over the interned strings. -/// -/// Rating varies between **bad**, **ok**, **good** and **best**. -/// -/// | Scenario | Rating | -/// |:------------|:--------:| -/// | Fill | **good** | -/// | Resolve | **best** | -/// | Allocations | **good** | -/// | Footprint | **ok** | -/// | Supports `get_or_intern_static` | **yes** | -/// | `Send` + `Sync` | **yes** | -/// | Contiguous | **yes** | -/// | Iteration | **best** | +/// An interner backend that reduces memory allocations by using buckets. +/// +/// # Overview +/// This interner uses fixed-size buckets to store interned strings. Each bucket is +/// allocated once and holds a set number of strings. When a bucket becomes full, a new +/// bucket is allocated to hold more strings. Buckets are never deallocated, which reduces +/// the overhead of frequent memory allocations and copying. +/// +/// ## Trade-offs +/// - **Advantages:** +/// - Strings in already used buckets remain valid and accessible even as new strings +/// are added. +/// - **Disadvantages:** +/// - Slightly slower access times due to double indirection (looking up the string +/// involves an extra level of lookup through the bucket). +/// - Memory may be used inefficiently if many buckets are allocated but only partially +/// filled because of large strings. +/// +/// ## Use Cases +/// This backend is ideal when interned strings must remain valid even after new ones are +/// added.general use +/// +/// Refer to the [comparison table][crate::_docs::comparison_table] for comparison with +/// other backends. +/// +/// [matklad's blog post]: +/// https://matklad.github.io/2020/03/22/fast-simple-rust-interner.html #[derive(Debug)] -pub struct BucketBackend { +pub struct BucketBackend<'i, S: Symbol = DefaultSymbol> { spans: Vec, head: FixedString, full: Vec, - marker: PhantomData S>, + marker: PhantomBackend<'i, Self>, } /// # Safety @@ -55,16 +49,16 @@ pub struct BucketBackend { /// The bucket backend requires a manual [`Send`] impl because it is self /// referential. When cloning a bucket backend a deep clone is performed and /// all references to itself are updated for the clone. -unsafe impl Send for BucketBackend where S: Symbol {} +unsafe impl<'i, S> Send for BucketBackend<'i, S> where S: Symbol {} /// # Safety /// /// The bucket backend requires a manual [`Send`] impl because it is self /// referential. Those references won't escape its own scope and also /// the bucket backend has no interior mutability. -unsafe impl Sync for BucketBackend where S: Symbol {} +unsafe impl<'i, S> Sync for BucketBackend<'i, S> where S: Symbol {} -impl Default for BucketBackend { +impl<'i, S: Symbol> Default for BucketBackend<'i, S> { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self { @@ -76,10 +70,14 @@ impl Default for BucketBackend { } } -impl Backend for BucketBackend +impl<'i, S> Backend<'i> for BucketBackend<'i, S> where S: Symbol, { + type Access<'local> = &'local str + where + Self: 'local, + 'i: 'local; type Symbol = S; type Iter<'a> = Iter<'a, S> @@ -136,7 +134,7 @@ where } } -impl BucketBackend +impl<'i, S> BucketBackend<'i, S> where S: Symbol, { @@ -167,7 +165,7 @@ where } } -impl Clone for BucketBackend { +impl<'i, S: Symbol> Clone for BucketBackend<'i, S> { fn clone(&self) -> Self { // For performance reasons we copy all cloned strings into a single cloned // head string leaving the cloned `full` empty. @@ -191,9 +189,9 @@ impl Clone for BucketBackend { } } -impl Eq for BucketBackend where S: Symbol {} +impl<'i, S> Eq for BucketBackend<'i, S> where S: Symbol {} -impl PartialEq for BucketBackend +impl<'i, S> PartialEq for BucketBackend<'i, S> where S: Symbol, { @@ -203,12 +201,12 @@ where } } -impl<'a, S> IntoIterator for &'a BucketBackend +impl<'i, 'l, S> IntoIterator for &'l BucketBackend<'i, S> where S: Symbol, { - type Item = (S, &'a str); - type IntoIter = Iter<'a, S>; + type Item = (S, &'l str); + type IntoIter = Iter<'l, S>; #[cfg_attr(feature = "inline-more", inline)] fn into_iter(self) -> Self::IntoIter { @@ -216,14 +214,14 @@ where } } -pub struct Iter<'a, S> { - iter: Enumerate>, +pub struct Iter<'l, S> { + iter: Enumerate>, symbol_marker: PhantomData S>, } -impl<'a, S> Iter<'a, S> { +impl<'i, 'l, S: Symbol> Iter<'l, S> { #[cfg_attr(feature = "inline-more", inline)] - pub fn new(backend: &'a BucketBackend) -> Self { + pub fn new(backend: &'l BucketBackend<'i, S>) -> Self { Self { iter: backend.spans.iter().enumerate(), symbol_marker: Default::default(), @@ -231,11 +229,11 @@ impl<'a, S> Iter<'a, S> { } } -impl<'a, S> Iterator for Iter<'a, S> +impl<'l, S> Iterator for Iter<'l, S> where S: Symbol, { - type Item = (S, &'a str); + type Item = (S, &'l str); #[inline] fn size_hint(&self) -> (usize, Option) { diff --git a/src/backend/buffer.rs b/src/backend/buffer.rs index c3b7c0b..6cbd5bf 100644 --- a/src/backend/buffer.rs +++ b/src/backend/buffer.rs @@ -1,46 +1,33 @@ #![cfg(feature = "backends")] -use super::Backend; +use super::{Backend, PhantomBackend}; use crate::{symbol::expect_valid_symbol, DefaultSymbol, Symbol}; use alloc::vec::Vec; -use core::{marker::PhantomData, mem, str}; +use core::{mem, str}; -/// An interner backend that appends all interned string information in a single buffer. +/// An interner backend that concatenates all interned string contents into one large +/// buffer [`Vec`]. Unlike [`StringBackend`][crate::backend::StringBackend], string +/// lengths are stored in the same buffer as strings preceeding the respective string data. /// -/// # Usage Hint +/// ## Trade-offs +/// - **Advantages:** +/// - Accessing interned strings is fast, as it requires a single lookup. +/// - **Disadvantages:** +/// - Iteration is slow because it requires consecutive reading of lengths to advance. /// -/// Use this backend if memory consumption is what matters most to you. -/// Note though that unlike all other backends symbol values are not contigous! +/// ## Use Cases +/// This backend is ideal for storing many small (<255 characters) strings. /// -/// # Usage -/// -/// - **Fill:** Efficiency of filling an empty string interner. -/// - **Resolve:** Efficiency of interned string look-up given a symbol. -/// - **Allocations:** The number of allocations performed by the backend. -/// - **Footprint:** The total heap memory consumed by the backend. -/// - **Contiguous:** True if the returned symbols have contiguous values. -/// - **Iteration:** Efficiency of iterating over the interned strings. -/// -/// Rating varies between **bad**, **ok**, **good** and **best**. -/// -/// | Scenario | Rating | -/// |:------------|:--------:| -/// | Fill | **best** | -/// | Resolve | **bad** | -/// | Allocations | **best** | -/// | Footprint | **best** | -/// | Supports `get_or_intern_static` | **no** | -/// | `Send` + `Sync` | **yes** | -/// | Contiguous | **no** | -/// | Iteration | **bad** | +/// Refer to the [comparison table][crate::_docs::comparison_table] for comparison with +/// other backends. #[derive(Debug)] -pub struct BufferBackend { +pub struct BufferBackend<'i, S: Symbol = DefaultSymbol> { len_strings: usize, buffer: Vec, - marker: PhantomData S>, + marker: PhantomBackend<'i, Self>, } -impl PartialEq for BufferBackend +impl<'i, S> PartialEq for BufferBackend<'i, S> where S: Symbol, { @@ -49,9 +36,9 @@ where } } -impl Eq for BufferBackend where S: Symbol {} +impl<'i, S> Eq for BufferBackend<'i, S> where S: Symbol {} -impl Clone for BufferBackend { +impl<'i, S: Symbol> Clone for BufferBackend<'i, S> { fn clone(&self) -> Self { Self { len_strings: self.len_strings, @@ -61,7 +48,7 @@ impl Clone for BufferBackend { } } -impl Default for BufferBackend { +impl<'i, S: Symbol> Default for BufferBackend<'i, S> { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self { @@ -72,7 +59,7 @@ impl Default for BufferBackend { } } -impl BufferBackend +impl<'i, S> BufferBackend<'i, S> where S: Symbol, { @@ -147,15 +134,19 @@ where } } -impl Backend for BufferBackend +impl<'i, S> Backend<'i> for BufferBackend<'i, S> where S: Symbol, { + type Access<'l> = &'l str + where + Self: 'l; type Symbol = S; - type Iter<'a> - = Iter<'a, S> + type Iter<'l> + = Iter<'i, 'l, S> where - Self: 'a; + 'i: 'l, + Self: 'l; #[cfg_attr(feature = "inline-more", inline)] fn with_capacity(capacity: usize) -> Self { @@ -307,12 +298,12 @@ fn decode_var_usize_cold(buffer: &[u8]) -> Option<(usize, usize)> { Some((result, i + 1)) } -impl<'a, S> IntoIterator for &'a BufferBackend +impl<'i, 'l, S> IntoIterator for &'l BufferBackend<'i, S> where S: Symbol, { - type Item = (S, &'a str); - type IntoIter = Iter<'a, S>; + type Item = (S, &'l str); + type IntoIter = Iter<'i, 'l, S>; #[cfg_attr(feature = "inline-more", inline)] fn into_iter(self) -> Self::IntoIter { @@ -320,15 +311,15 @@ where } } -pub struct Iter<'a, S> { - backend: &'a BufferBackend, +pub struct Iter<'i, 'l, S: Symbol> { + backend: &'l BufferBackend<'i, S>, remaining: usize, next: usize, } -impl<'a, S> Iter<'a, S> { +impl<'i, 'l, S: Symbol> Iter<'i, 'l, S> { #[cfg_attr(feature = "inline-more", inline)] - pub fn new(backend: &'a BufferBackend) -> Self { + pub fn new(backend: &'l BufferBackend<'i, S>) -> Self { Self { backend, remaining: backend.len_strings, @@ -337,11 +328,11 @@ impl<'a, S> Iter<'a, S> { } } -impl<'a, S> Iterator for Iter<'a, S> +impl<'i, 'l, S> Iterator for Iter<'i, 'l, S> where S: Symbol, { - type Item = (S, &'a str); + type Item = (S, &'l str); #[inline] fn size_hint(&self) -> (usize, Option) { @@ -365,7 +356,7 @@ where } } -impl ExactSizeIterator for Iter<'_, S> +impl<'i, S> ExactSizeIterator for Iter<'i, '_, S> where S: Symbol, { diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 1609291..374c0c0 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -14,21 +14,43 @@ use crate::Symbol; /// The default backend recommended for general use. #[cfg(feature = "backends")] -pub type DefaultBackend = StringBackend; +pub type DefaultBackend<'i> = StringBackend<'i, crate::DefaultSymbol>; + +/// [`PhantomData`][std::marker::PhantomData] wrapper that describes how a [`Backend`] +/// implementor uses lifetime `'i` and [`B::Symbol`][Backend::Symbol]. +#[allow(type_alias_bounds)] // included for clarity +type PhantomBackend<'i, B: Backend<'i>> = std::marker::PhantomData< + // 'i is invariant, Symbol is covariant + Send + Sync + (core::cell::Cell<&'i ()>, fn() -> >::Symbol) +>; /// Types implementing this trait may act as backends for the string interner. /// /// The job of a backend is to actually store, manage and organize the interned /// strings. Different backends have different trade-offs. Users should pick /// their backend with hinsight of their personal use-case. -pub trait Backend: Default { +pub trait Backend<'i>: Default { /// The symbol used by the string interner backend. type Symbol: Symbol; + /// Describes the lifetime of returned string. + /// + /// If interned strings can move between insertion this type will be + /// `&'local str` - indicating that resolved `str` is only valid while + /// container isn't mutably accessed. + /// + /// If interned strings can't move then this type is `&'container str`, + /// indicating that resolved `str` are valid for as long as interner exists. + type Access<'l>: AsRef + where + Self: 'l, + 'i: 'l; + /// The iterator over the symbols and their strings. - type Iter<'a>: Iterator + type Iter<'l>: Iterator)> where - Self: 'a; + 'i: 'l, + Self: 'l; /// Creates a new backend for the given capacity. /// @@ -61,7 +83,7 @@ pub trait Backend: Default { fn shrink_to_fit(&mut self); /// Resolves the given symbol to its original string contents. - fn resolve(&self, symbol: Self::Symbol) -> Option<&str>; + fn resolve(&self, symbol: Self::Symbol) -> Option>; /// Resolves the given symbol to its original string contents. /// @@ -72,7 +94,7 @@ pub trait Backend: Default { /// by the [`intern`](`Backend::intern`) or /// [`intern_static`](`Backend::intern_static`) methods of the same /// interner backend. - unsafe fn resolve_unchecked(&self, symbol: Self::Symbol) -> &str; + unsafe fn resolve_unchecked(&self, symbol: Self::Symbol) -> Self::Access<'_>; /// Creates an iterator that yields all interned strings and their symbols. fn iter(&self) -> Self::Iter<'_>; diff --git a/src/backend/string.rs b/src/backend/string.rs index 307dd60..03d0276 100644 --- a/src/backend/string.rs +++ b/src/backend/string.rs @@ -1,47 +1,36 @@ #![cfg(feature = "backends")] -use super::Backend; +use super::{Backend, PhantomBackend}; use crate::{symbol::expect_valid_symbol, DefaultSymbol, Symbol}; use alloc::{string::String, vec::Vec}; -use core::{iter::Enumerate, marker::PhantomData, slice}; +use core::{iter::Enumerate, slice}; -/// An interner backend that accumulates all interned string contents into one string. +/// An interner backend that concatenates all interned string contents into one large +/// buffer and keeps track of string bounds in a separate [`Vec`]. +/// +/// Implementation is inspired by [CAD97's](https://github.com/CAD97) +/// [`strena`](https://github.com/CAD97/strena) crate. /// -/// # Note +/// ## Trade-offs +/// - **Advantages:** +/// - Separated length tracking allows fast iteration. +/// - **Disadvantages:** +/// - Many insertions separated by external allocations can cause the buffer to drift +/// far away (in memory) from `Vec` storing string ends, which impedes performance of +/// all interning operations. +/// - Resolving a symbol requires two heap lookups because data and length are stored in +/// separate containers. /// -/// Implementation inspired by [CAD97's](https://github.com/CAD97) research -/// project [`strena`](https://github.com/CAD97/strena). +/// ## Use Cases +/// This backend is good for storing fewer large strings and for general use. /// -/// # Usage Hint -/// -/// Use this backend if runtime performance is what matters most to you. -/// -/// # Usage -/// -/// - **Fill:** Efficiency of filling an empty string interner. -/// - **Resolve:** Efficiency of interned string look-up given a symbol. -/// - **Allocations:** The number of allocations performed by the backend. -/// - **Footprint:** The total heap memory consumed by the backend. -/// - **Contiguous:** True if the returned symbols have contiguous values. -/// - **Iteration:** Efficiency of iterating over the interned strings. -/// -/// Rating varies between **bad**, **ok**, **good** and **best**. -/// -/// | Scenario | Rating | -/// |:------------|:--------:| -/// | Fill | **good** | -/// | Resolve | **ok** | -/// | Allocations | **good** | -/// | Footprint | **good** | -/// | Supports `get_or_intern_static` | **no** | -/// | `Send` + `Sync` | **yes** | -/// | Contiguous | **yes** | -/// | Iteration | **good** | +/// Refer to the [comparison table][crate::_docs::comparison_table] for comparison with +/// other backends. #[derive(Debug)] -pub struct StringBackend { +pub struct StringBackend<'i, S: Symbol = DefaultSymbol> { ends: Vec, buffer: String, - marker: PhantomData S>, + marker: PhantomBackend<'i, Self>, } /// Represents a `[from, to)` index into the `StringBackend` buffer. @@ -51,7 +40,7 @@ pub struct Span { to: usize, } -impl PartialEq for StringBackend +impl<'i, S> PartialEq for StringBackend<'i, S> where S: Symbol, { @@ -68,9 +57,9 @@ where } } -impl Eq for StringBackend where S: Symbol {} +impl<'i, S> Eq for StringBackend<'i, S> where S: Symbol {} -impl Clone for StringBackend { +impl<'i, S: Symbol> Clone for StringBackend<'i, S> { fn clone(&self) -> Self { Self { ends: self.ends.clone(), @@ -80,7 +69,7 @@ impl Clone for StringBackend { } } -impl Default for StringBackend { +impl<'i, S: Symbol> Default for StringBackend<'i, S> { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { Self { @@ -91,7 +80,7 @@ impl Default for StringBackend { } } -impl StringBackend +impl<'i, S> StringBackend<'i, S> where S: Symbol, { @@ -144,15 +133,17 @@ where } } -impl Backend for StringBackend +impl<'i, S> Backend<'i> for StringBackend<'i, S> where S: Symbol, { + type Access<'l> = &'l str where Self: 'l; + type Symbol = S; - type Iter<'a> - = Iter<'a, S> + type Iter<'l> + = Iter<'i, 'l, S> where - Self: 'a; + Self: 'l; #[cfg_attr(feature = "inline-more", inline)] fn with_capacity(cap: usize) -> Self { @@ -194,12 +185,12 @@ where } } -impl<'a, S> IntoIterator for &'a StringBackend +impl<'i, 'l, S> IntoIterator for &'l StringBackend<'i, S> where - S: Symbol, + S: Symbol + 'l, { - type Item = (S, &'a str); - type IntoIter = Iter<'a, S>; + type Item = (S, &'l str); + type IntoIter = Iter<'i, 'l, S>; #[cfg_attr(feature = "inline-more", inline)] fn into_iter(self) -> Self::IntoIter { @@ -207,15 +198,15 @@ where } } -pub struct Iter<'a, S> { - backend: &'a StringBackend, +pub struct Iter<'i, 'l, S: Symbol> { + backend: &'l StringBackend<'i, S>, start: usize, - ends: Enumerate>, + ends: Enumerate>, } -impl<'a, S> Iter<'a, S> { +impl<'i, 'l, S: Symbol> Iter<'i, 'l, S> { #[cfg_attr(feature = "inline-more", inline)] - pub fn new(backend: &'a StringBackend) -> Self { + pub fn new(backend: &'l StringBackend<'i, S>) -> Self { Self { backend, start: 0, @@ -224,11 +215,11 @@ impl<'a, S> Iter<'a, S> { } } -impl<'a, S> Iterator for Iter<'a, S> +impl<'i, 'l, S> Iterator for Iter<'i, 'l, S> where S: Symbol, { - type Item = (S, &'a str); + type Item = (S, &'l str); #[inline] fn size_hint(&self) -> (usize, Option) { diff --git a/src/docs.rs b/src/docs.rs new file mode 100644 index 0000000..cf063ed --- /dev/null +++ b/src/docs.rs @@ -0,0 +1,378 @@ +//! Crate documentation supplements +//! +//! + +/// Stylesheet that adds simple clip-path based icons. +/// +/// They're used like so: +/// ```html +/// +/// ``` +/// +/// `icon-name`: is a meaningful description of icon meaning. +/// +/// This satisfies ARIA requirements and looks as expected. +macro_rules! icons { + () => { r#""# + } +} + +/// Generates glue that: +/// - Shows the correct (provided) title in TOC +/// - Cleans up page display +macro_rules! doc_item { + ($title: literal) => { concat![ + "# ", $title, "\n\n", + r#""#]} +} + +pub mod _01_comparison_table { + #![doc = doc_item!("Comparison Table")] + //!
+ //!
+ //! + //! | **Property** | [`BucketBackend`] | [`StringBackend`] | [`BufferBackend`] | + //! |:-----------------------------------------------------|:--:|:--:|:--:| + //! | [**Insertion**](#insertion) | | | | + //! | [**Resolution**](#resolution) | | | | + //! | [**Allocations**](#allocations) | | | | + //! | [**Memory footprint**](#memory-footprint) | | | | + //! | [**Iteration**](#iteration) | | | | + //! | [**Contiguous**](#contiguous) | | | | + //! | [**Intern `'static`**](#intern-static) | | | | + //! | [**Concurrent symbols**](#concurrent-symbols) | | | | + //! | [**Concurrent storage**](#concurrent-storage) | | | | + //! + //!
+ //! + //! #### Legend + //! + //!
    + //!
  • Best
  • + //!
  • Good
  • + //!
  • Ok
  • + //!
  • Bad
  • + //!
+ //!
    + //!
  • Yes
  • + //!
  • No
  • + //!
+ //!
+ //!
+ //! + //! + //! + //! #### Properties + //! + //! ##### Insertion + //! + //! Efficiency of interning new strings. + //! + //! This metric is based on benchmarking results. + //! + //! ##### Resolution + //! + //! Efficiency of resolving a symbol into a string reference. + //! + //! ##### Allocations + //! + //! The number of (re-)allocations performed by the backend. + //! + //! ##### Memory footprint + //! + //! Heap memory consumtion characteristics for the backend. + //! + //! ##### Iteration + //! + //! Efficiency of iterating over the interned strings. + //! + //! ##### Contiguous + //! + //! True if the interned symbols are contiguously stored in memory. + //! + //! ##### Intern `'static` + //! + //! True if interner can resolve symbols to statically allocated strings + //! that have been inserted using [`StringInterner::get_or_intern_static`]. + //! + //! ##### Concurrent symbols + //! + //! True if returned symbols are [`Send`] + [`Sync`]. + //! + //! ##### Concurrent storage + //! + //! True if interned strings are [`Send`] + [`Sync`] while the interner is + //! kept alive. This means resolved strings won't be moved until the + //! interner is dropped. + #![doc = icons!()] + + use crate::interner::*; + use crate::backend::*; + use crate::symbol::*; + use std::marker::*; +} +pub use _01_comparison_table as comparison_table; diff --git a/src/interner.rs b/src/interner.rs index 3841a47..905838e 100644 --- a/src/interner.rs +++ b/src/interner.rs @@ -28,19 +28,19 @@ where /// - This maps from `string` type to `symbol` type. /// - [`StringInterner::resolve`]: To resolve your already interned strings. /// - This maps from `symbol` type to `string` type. -pub struct StringInterner +pub struct StringInterner<'i, B, H = DefaultHashBuilder> where - B: Backend, + B: Backend<'i>, { - dedup: HashMap<::Symbol, (), ()>, + dedup: HashMap<>::Symbol, (), ()>, hasher: H, backend: B, } -impl Debug for StringInterner +impl<'i, B, H> Debug for StringInterner<'i, B, H> where - B: Backend + Debug, - ::Symbol: Symbol + Debug, + B: Backend<'i> + Debug, + >::Symbol: Symbol + Debug, H: BuildHasher, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -52,17 +52,17 @@ where } #[cfg(feature = "backends")] -impl Default for StringInterner { +impl<'i> Default for StringInterner<'i, crate::DefaultBackend<'i>> { #[cfg_attr(feature = "inline-more", inline)] fn default() -> Self { StringInterner::new() } } -impl Clone for StringInterner +impl<'i, B, H> Clone for StringInterner<'i, B, H> where - B: Backend + Clone, - ::Symbol: Symbol, + B: Backend<'i> + Clone, + >::Symbol: Symbol, H: BuildHasher + Clone, { fn clone(&self) -> Self { @@ -74,10 +74,10 @@ where } } -impl PartialEq for StringInterner +impl<'i, B, H> PartialEq for StringInterner<'i, B, H> where - B: Backend + PartialEq, - ::Symbol: Symbol, + B: Backend<'i> + PartialEq, + >::Symbol: Symbol, H: BuildHasher, { fn eq(&self, rhs: &Self) -> bool { @@ -85,18 +85,18 @@ where } } -impl Eq for StringInterner +impl<'i, B, H> Eq for StringInterner<'i, B, H> where - B: Backend + Eq, - ::Symbol: Symbol, + B: Backend<'i> + Eq, + >::Symbol: Symbol, H: BuildHasher, { } -impl StringInterner +impl<'i, B, H> StringInterner<'i, B, H> where - B: Backend, - ::Symbol: Symbol, + B: Backend<'i>, + >::Symbol: Symbol, H: BuildHasher + Default, { /// Creates a new empty `StringInterner`. @@ -120,10 +120,10 @@ where } } -impl StringInterner +impl<'i, B, H> StringInterner<'i, B, H> where - B: Backend, - ::Symbol: Symbol, + B: Backend<'i>, + >::Symbol: Symbol, H: BuildHasher, { /// Creates a new empty `StringInterner` with the given hasher. @@ -162,7 +162,7 @@ where /// /// Can be used to query if a string has already been interned without interning. #[inline] - pub fn get(&self, string: T) -> Option<::Symbol> + pub fn get(&self, string: T) -> Option<>::Symbol> where T: AsRef, { @@ -178,7 +178,7 @@ where .from_hash(hash, |symbol| { // SAFETY: This is safe because we only operate on symbols that // we receive from our backend making them valid. - string == unsafe { backend.resolve_unchecked(*symbol) } + string == unsafe { backend.resolve_unchecked(*symbol) }.as_ref() }) .map(|(&symbol, &())| symbol) } @@ -193,8 +193,8 @@ where fn get_or_intern_using( &mut self, string: T, - intern_fn: fn(&mut B, T) -> ::Symbol, - ) -> ::Symbol + intern_fn: fn(&mut B, T) -> >::Symbol, + ) -> >::Symbol where T: Copy + Hash + AsRef + for<'a> PartialEq<&'a str>, { @@ -207,7 +207,7 @@ where let entry = dedup.raw_entry_mut().from_hash(hash, |symbol| { // SAFETY: This is safe because we only operate on symbols that // we receive from our backend making them valid. - string == unsafe { backend.resolve_unchecked(*symbol) } + string == unsafe { backend.resolve_unchecked(*symbol) }.as_ref() }); use hashbrown::hash_map::RawEntryMut; let (&mut symbol, &mut ()) = match entry { @@ -218,7 +218,7 @@ where // SAFETY: This is safe because we only operate on symbols that // we receive from our backend making them valid. let string = unsafe { backend.resolve_unchecked(*symbol) }; - make_hash(hasher, string) + make_hash(hasher, string.as_ref()) }) } }; @@ -234,7 +234,7 @@ where /// If the interner already interns the maximum number of strings possible /// by the chosen symbol type. #[inline] - pub fn get_or_intern(&mut self, string: T) -> ::Symbol + pub fn get_or_intern(&mut self, string: T) -> >::Symbol where T: AsRef, { @@ -242,20 +242,24 @@ where } /// Interns the given `'static` string. - /// + /// /// Returns a symbol for resolution into the original string. + /// + /// If the backend supports [`'static` interning][crate::_docs::comparison_table], + /// later calls to this or [`get_or_intern`][StringInterner::get_or_intern] function + /// will return a symbol that resolves to the original `&'static str` reference. /// /// # Note /// - /// This is more efficient than [`StringInterner::get_or_intern`] since it might - /// avoid some memory allocations if the backends supports this. + /// This is more efficient than [`StringInterner::get_or_intern`] since it might avoid + /// some memory allocations if the backends supports this. /// /// # Panics /// - /// If the interner already interns the maximum number of strings possible - /// by the chosen symbol type. + /// If the interner already interns the maximum number of strings possible by the + /// chosen symbol type. #[inline] - pub fn get_or_intern_static(&mut self, string: &'static str) -> ::Symbol { + pub fn get_or_intern_static(&mut self, string: &'static str) -> >::Symbol { self.get_or_intern_using(string, B::intern_static) } @@ -266,7 +270,7 @@ where /// Returns the string for the given `symbol`` if any. #[inline] - pub fn resolve(&self, symbol: ::Symbol) -> Option<&str> { + pub fn resolve(&self, symbol: >::Symbol) -> Option<>::Access<'_>> { self.backend.resolve(symbol) } @@ -277,21 +281,21 @@ where /// It is the caller's responsibility to provide this method with `symbol`s /// that are valid for the [`StringInterner`]. #[inline] - pub unsafe fn resolve_unchecked(&self, symbol: ::Symbol) -> &str { + pub unsafe fn resolve_unchecked(&self, symbol: >::Symbol) -> >::Access<'_> { unsafe { self.backend.resolve_unchecked(symbol) } } /// Returns an iterator that yields all interned strings and their symbols. #[inline] - pub fn iter(&self) -> ::Iter<'_> { + pub fn iter(&self) -> >::Iter<'_> { self.backend.iter() } } -impl FromIterator for StringInterner +impl<'i, B, H, T> FromIterator for StringInterner<'i, B, H> where - B: Backend, - ::Symbol: Symbol, + B: Backend<'i>, + >::Symbol: Symbol, H: BuildHasher + Default, T: AsRef, { @@ -307,10 +311,10 @@ where } } -impl Extend for StringInterner +impl<'i, B, H, T> Extend for StringInterner<'i, B, H> where - B: Backend, - ::Symbol: Symbol, + B: Backend<'i>, + >::Symbol: Symbol, H: BuildHasher, T: AsRef, { @@ -324,15 +328,15 @@ where } } -impl<'a, B, H> IntoIterator for &'a StringInterner +impl<'i, 'l, B, H> IntoIterator for &'l StringInterner<'i, B, H> where - B: Backend, - ::Symbol: Symbol, - &'a B: IntoIterator::Symbol, &'a str)>, + B: Backend<'i>, + >::Symbol: Symbol, + &'l B: IntoIterator>::Symbol, &'l str)>, H: BuildHasher, { - type Item = (::Symbol, &'a str); - type IntoIter = <&'a B as IntoIterator>::IntoIter; + type Item = (>::Symbol, &'l str); + type IntoIter = <&'l B as IntoIterator>::IntoIter; #[cfg_attr(feature = "inline-more", inline)] fn into_iter(self) -> Self::IntoIter { diff --git a/src/lib.rs b/src/lib.rs index 8f313a6..61f7c84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,19 @@ #![doc(html_root_url = "https://docs.rs/crate/string-interner/0.18.0")] #![warn(unsafe_op_in_unsafe_fn, clippy::redundant_closure_for_method_calls)] -//! Caches strings efficiently, with minimal memory footprint and associates them with unique symbols. -//! These symbols allow constant time comparisons and look-ups to the underlying interned strings. -//! -//! ### Example: Interning & Symbols +//! Caches strings efficiently, with minimal memory footprint and associates them with +//! unique symbols. These symbols allow constant time equality comparison and look-ups to +//! the underlying interned strings. +//! +//! For more information on purpose of string interning, refer to the corresponding +//! [wikipedia article]. +//! +//! See the [**comparison table**](crate::_docs::comparison_table) for a detailed +//! comparison summary of different backends. +//! +//! ## Examples +//! +//! #### Interning & Symbols //! //! ``` //! use string_interner::StringInterner; @@ -21,7 +30,7 @@ //! assert_eq!(sym1, sym3); // same! //! ``` //! -//! ### Example: Creation by `FromIterator` +//! #### Creation by `FromIterator` //! //! ``` //! # use string_interner::DefaultStringInterner; @@ -30,7 +39,7 @@ //! .collect::(); //! ``` //! -//! ### Example: Look-up +//! #### Look-up //! //! ``` //! # use string_interner::StringInterner; @@ -39,7 +48,7 @@ //! assert_eq!(interner.resolve(sym), Some("Banana")); //! ``` //! -//! ### Example: Iteration +//! #### Iteration //! //! ``` //! # use string_interner::{DefaultStringInterner, Symbol}; @@ -49,12 +58,12 @@ //! } //! ``` //! -//! ### Example: Use Different Backend +//! #### Use Different Backend //! //! ``` //! # use string_interner::StringInterner; //! use string_interner::backend::BufferBackend; -//! type Interner = StringInterner; +//! type Interner<'i> = StringInterner<'i, BufferBackend<'i>>; //! let mut interner = Interner::new(); //! let sym1 = interner.get_or_intern("Tiger"); //! let sym2 = interner.get_or_intern("Horse"); @@ -63,12 +72,12 @@ //! assert_eq!(sym1, sym3); // same! //! ``` //! -//! ### Example: Use Different Backend & Symbol +//! #### Use Different Backend & Symbol //! //! ``` //! # use string_interner::StringInterner; //! use string_interner::{backend::BucketBackend, symbol::SymbolU16}; -//! type Interner = StringInterner>; +//! type Interner<'i> = StringInterner<'i, BucketBackend<'i, SymbolU16>>; //! let mut interner = Interner::new(); //! let sym1 = interner.get_or_intern("Tiger"); //! let sym2 = interner.get_or_intern("Horse"); @@ -79,44 +88,38 @@ //! //! ## Backends //! -//! The `string_interner` crate provides different backends with different strengths. -//! The table below compactly shows when to use which backend according to the following -//! performance characteristics. -//! -//! - **Fill:** Efficiency of filling an empty string interner. -//! - **Resolve:** Efficiency of resolving a symbol of an interned string. -//! - **Allocations:** The number of allocations performed by the backend. -//! - **Footprint:** The total heap memory consumed by the backend. -//! - **Contiguous:** True if the returned symbols have contiguous values. -//! - **Iteration:** Efficiency of iterating over the interned strings. -//! -//! | **Property** | **BucketBackend** | **StringBackend** | **BufferBackend** | -//! |:-------------|:-----------------:|:-----------------:|:-----------------:| -//! | **Fill** | ok | good | best | -//! | **Resolve** | best | good | bad | -//! | Allocations | ok | good | best | -//! | Footprint | ok | good | best | -//! | Contiguous | yes | yes | no | -//! | Iteration | best | good | bad | +//! The `string_interner` crate provides different backends with different strengths.
+//! +//! #### [Bucket Backend](backend/struct.BucketBackend.html) +//! +//! Stores strings in buckets which stay allocated for the lifespan of [`StringInterner`]. +//! This allows resolved symbols to be used even after new strings have been interned. //! -//! ## When to use which backend? +//! **Ideal for:** storing strings in persistent location in memory //! -//! ### Bucket Backend +//! #### [String Backend](backend/struct.StringBackend.html) +//! +//! Concatenates all interned string contents into one large buffer +//! [`String`][alloc::string::String], keeping interned string lenghts in a separate +//! [`Vec`][alloc::vec::Vec]. //! -//! Given the table above the `BucketBackend` might seem inferior to the other backends. -//! However, it allows to efficiently intern `&'static str` and avoids deallocations. +//! **Ideal for:** general use //! -//! ### String Backend +//! #### [Buffer Backend](backend/struct.BufferBackend.html) //! -//! Overall the `StringBackend` performs really well and therefore is the backend -//! that the `StringInterner` uses by default. +//! Concatenates all interned string contents into one large buffer +//! [`String`][alloc::string::String], and keeps interned string lenghts as prefixes. //! -//! ### Buffer Backend -//! -//! The `BufferBackend` is in some sense similar to the `StringBackend` on steroids. -//! Some operations are even slightly more efficient and it consumes less memory. -//! However, all this is at the costs of a less efficient resolution of symbols. -//! Note that the symbols generated by the `BufferBackend` are not contiguous. +//! **Ideal for:** storing many small (<255 characters) strings +//! +//! [Comparison table][crate::_docs::comparison_table] shows a high-level overview of +//! different backend characteristics. +//! +//! [wikipedia article]: https://en.wikipedia.org/wiki/String_interning + +#[cfg(doc)] +#[path ="docs.rs"] +pub mod _docs; extern crate alloc; #[cfg(feature = "std")] @@ -132,8 +135,8 @@ pub mod symbol; /// A convenience [`StringInterner`] type based on the [`DefaultBackend`]. #[cfg(feature = "backends")] -pub type DefaultStringInterner = - self::interner::StringInterner; +pub type DefaultStringInterner<'i, B = DefaultBackend<'i>, H = DefaultHashBuilder> = + self::interner::StringInterner<'i, B, H>; #[cfg(feature = "backends")] #[doc(inline)] diff --git a/src/serde_impl.rs b/src/serde_impl.rs index 0bf79fb..01fcb62 100644 --- a/src/serde_impl.rs +++ b/src/serde_impl.rs @@ -6,11 +6,11 @@ use serde::{ ser::{Serialize, SerializeSeq, Serializer}, }; -impl Serialize for StringInterner +impl<'i, B, H> Serialize for StringInterner<'i, B, H> where - B: Backend, - ::Symbol: Symbol, - for<'a> &'a B: IntoIterator::Symbol, &'a str)>, + B: Backend<'i>, + >::Symbol: Symbol, + for<'l> &'l B: IntoIterator>::Symbol, &'l str)>, H: BuildHasher, { fn serialize(&self, serializer: T) -> Result @@ -25,13 +25,13 @@ where } } -impl<'de, B, H> Deserialize<'de> for StringInterner +impl<'i: 'de, 'de, B, H> Deserialize<'de> for StringInterner<'i, B, H> where - B: Backend, - ::Symbol: Symbol, + B: Backend<'i>, + >::Symbol: Symbol, H: BuildHasher + Default, { - fn deserialize(deserializer: D) -> Result, D::Error> + fn deserialize(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { @@ -39,19 +39,19 @@ where } } -struct StringInternerVisitor +struct StringInternerVisitor<'i, B, H> where - B: Backend, - ::Symbol: Symbol, + B: Backend<'i>, + >::Symbol: Symbol, H: BuildHasher, { - mark: marker::PhantomData<(::Symbol, B, H)>, + mark: marker::PhantomData<(>::Symbol, B, H)>, } -impl Default for StringInternerVisitor +impl<'i, B, H> Default for StringInternerVisitor<'i, B, H> where - B: Backend, - ::Symbol: Symbol, + B: Backend<'i>, + >::Symbol: Symbol, H: BuildHasher, { fn default() -> Self { @@ -61,13 +61,13 @@ where } } -impl<'de, B, H> Visitor<'de> for StringInternerVisitor +impl<'i: 'de, 'de, B, H> Visitor<'de> for StringInternerVisitor<'i, B, H> where - B: Backend, - ::Symbol: Symbol, + B: Backend<'i>, + >::Symbol: Symbol, H: BuildHasher + Default, { - type Value = StringInterner; + type Value = StringInterner<'i, B, H>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("Expected a contiguous sequence of strings.") diff --git a/tests/tests.rs b/tests/tests.rs index 1f4a55c..e1b05cc 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -33,7 +33,7 @@ pub trait BackendStats { const NAME: &'static str; } -impl BackendStats for backend::BucketBackend { +impl BackendStats for backend::BucketBackend<'_, DefaultSymbol> { const MIN_OVERHEAD: f64 = 2.2; const MAX_OVERHEAD: f64 = 3.1; const MAX_ALLOCATIONS: usize = 65; @@ -41,7 +41,7 @@ impl BackendStats for backend::BucketBackend { const NAME: &'static str = "BucketBackend"; } -impl BackendStats for backend::StringBackend { +impl BackendStats for backend::StringBackend<'_, DefaultSymbol> { const MIN_OVERHEAD: f64 = 1.7; const MAX_OVERHEAD: f64 = 1.93; const MAX_ALLOCATIONS: usize = 62; @@ -49,7 +49,7 @@ impl BackendStats for backend::StringBackend { const NAME: &'static str = "StringBackend"; } -impl BackendStats for backend::BufferBackend { +impl BackendStats for backend::BufferBackend<'_, DefaultSymbol> { const MIN_OVERHEAD: f64 = 1.35; const MAX_OVERHEAD: f64 = 1.58; const MAX_ALLOCATIONS: usize = 43; @@ -68,9 +68,12 @@ pub struct ProfilingStats { } macro_rules! gen_tests_for_backend { - ( $backend:ty ) => { - type StringInterner = - string_interner::StringInterner<$backend, DefaultHashBuilder>; + ( $backend:ident ) => { + gen_tests_for_backend!($backend, symbol: DefaultSymbol); + }; + ( $backend:ident, symbol: $symbol: ident ) => { + type StringInterner<'i> = + string_interner::StringInterner<'i, backend::$backend<'i, $symbol>, DefaultHashBuilder>; fn profile_memory_usage(words: &[String]) -> ProfilingStats { ALLOCATOR.reset(); @@ -128,7 +131,7 @@ macro_rules! gen_tests_for_backend { }).collect::>(); println!(); - println!("Benchmark Memory Usage for {}", <$backend as BackendStats>::NAME); + println!("Benchmark Memory Usage for {}", as BackendStats>::NAME); let mut min_overhead = None; let mut max_overhead = None; let mut max_allocations = None; @@ -152,12 +155,12 @@ macro_rules! gen_tests_for_backend { } let actual_min_overhead = min_overhead.unwrap(); let actual_max_overhead = max_overhead.unwrap(); - let expect_min_overhead = <$backend as BackendStats>::MIN_OVERHEAD; - let expect_max_overhead = <$backend as BackendStats>::MAX_OVERHEAD; + let expect_min_overhead = as BackendStats>::MIN_OVERHEAD; + let expect_max_overhead = as BackendStats>::MAX_OVERHEAD; let actual_max_allocations = max_allocations.unwrap(); let actual_max_deallocations = max_deallocations.unwrap(); - let expect_max_allocations = <$backend as BackendStats>::MAX_ALLOCATIONS; - let expect_max_deallocations = <$backend as BackendStats>::MAX_DEALLOCATIONS; + let expect_max_allocations = as BackendStats>::MAX_ALLOCATIONS; + let expect_max_deallocations = as BackendStats>::MAX_DEALLOCATIONS; println!(); println!("- % min overhead = {:.02}%", actual_min_overhead * 100.0); @@ -168,28 +171,28 @@ macro_rules! gen_tests_for_backend { assert!( actual_min_overhead < expect_min_overhead, "{} string interner backend minimum memory overhead is greater than expected. expected = {:?}, actual = {:?}", - <$backend as BackendStats>::NAME, + as BackendStats>::NAME, expect_min_overhead, actual_min_overhead, ); assert!( actual_max_overhead < expect_max_overhead, "{} string interner backend maximum memory overhead is greater than expected. expected = {:?}, actual = {:?}", - <$backend as BackendStats>::NAME, + as BackendStats>::NAME, expect_max_overhead, actual_max_overhead, ); assert_eq!( actual_max_allocations, expect_max_allocations, "{} string interner backend maximum amount of allocations is greater than expected. expected = {:?}, actual = {:?}", - <$backend as BackendStats>::NAME, + as BackendStats>::NAME, expect_max_allocations, actual_max_allocations, ); assert_eq!( actual_max_deallocations, expect_max_deallocations, "{} string interner backend maximum amount of deallocations is greater than expected. expected = {:?}, actual = {:?}", - <$backend as BackendStats>::NAME, + as BackendStats>::NAME, expect_max_deallocations, actual_max_deallocations, ); @@ -414,17 +417,17 @@ macro_rules! gen_tests_for_backend { mod bucket_backend { use super::*; - gen_tests_for_backend!(backend::BucketBackend); + gen_tests_for_backend!(BucketBackend); } mod string_backend { use super::*; - gen_tests_for_backend!(backend::StringBackend); + gen_tests_for_backend!(StringBackend); } mod buffer_backend { use super::*; - gen_tests_for_backend!(backend::BufferBackend); + gen_tests_for_backend!(BufferBackend); }