Expand description
This crate does not require nor std nor alloc ❤️§Stacked set
This crate defines a so-called “stacked set” - a set-like interface that allows implementations without dynamic memory.
Interface itself can be summarized by StackedSet trait:
// (simplified)
trait StackedSet {
fn empty() -> Self;
fn contains(&self, item: impl Borrow<Self::Item>) -> bool;
fn extend(&mut self, new_item: Self::Item) -> Self; // (output is not really `Self`, but that's the basic idea)
fn fork(&mut self) -> Self; // (same here, it's not actually `Self`)
}§Picking the implementation
Currently, 4 implementations are provided:
- cons-like alloc-free implementation
Vec-based implementation (needsalloc)BTreeSet-based implementation (needsalloc)HashSet-based implementation (needsstd::hash)
All of them are feature-locked and cons implementation is the only one enabled by default.
§Usage example
Creating:
use stacked_set::{
StackedSet,
StackCons, // <- this implementation specifically is alloc-free!
};
let set = StackCons::<'static, i32>::empty();Don’t let random 'static lifetime confuse you - it means that this stacked set in particular has no dependency on any other stacked set below it. That is indeed correct, as empty set contains nothing.
Let’s add a 1 to the set:
let with_1 = set.extend(1);Now 1 is present in the set, while 2 is not:
assert!(with_1.contains(1));
assert!(!with_1.contains(&2));Note that both i32 and &i32 were passed here - contains method of this set accepts impl Borrow<i32>.
NOTE: you cannot use set, with with_1 is alive:
set.contains(42);
&with_1;This happens, because internally with_1 is thought to contain an exclusive borrow of set.
This part of design has nothing to do with stack-only implementation, but allows implementations with normal kind of collections, like Vec (see Collection trait below)
To remove 1 from the set, drop with_1 handle:
let _ = with_1;
// no more 1 in the set!
assert!(!set.contains(1));NOTE: this means that stacked set cannot be used as “out parameter”:
fn add_1(set: &mut impl StackedSet<Item = i32>) {
// add 1
set.extend(1);
}
let mut set = StackCons::<'static, i32>::empty();
add_1(&mut set);
// doesn't work :(
assert!(set.contains(1)); // <-- panic occurs hereYou can return the set itself though:
fn append_1<S: StackedSet<Item = i32>>(set: &mut S) -> impl StackedSet<Item = i32> + use<'_, S> {
set.extend(1)
}
let with_1 = append_1(&mut set);
// it works! :3
assert!(with_1.contains(1)); // <-- Rust no panic: Rust happy
core::mem::drop(with_1); // <-- must drop this variable to be able to use `set`
assert!(!set.contains(1));Please note however, that you can’t return more than 1 value in this way:
fn append_1_and_2<S: StackedSet<Item = i32>>(set: &mut S) -> impl StackedSet<Item = i32> + use<'_, S> {
let mut with_1 = set.extend(1)
let with_1_and_2 = with_1.extend(2);
with_1_and_2 // <-- can't return, as it references temporary value (`with_1`)
}
let _ = append_1_and_2(&mut set);So StackedSets are not suited well for this kind of task.
Instead, consider passing values to inner calls:
fn nested(mut set: impl StackedSet<Item = i32>, val: i32) {
if val == 0 {
for i in 1..=10 {
assert!(set.contains(i));
}
assert!(!set.contains(11));
assert!(!set.contains(0));
} else {
nested(set.extend(val), val-1);
}
}
let mut set = StackCons::empty();
nested(set, 10);(this is, in fact, the intended usecase)
You can also iterate over values in the set:
fn nested(mut set: impl StackedSet<Item = i32>, val: i32) {
if val == 0 {
let mut v = set.iter().copied().collect::<Vec<_>>();
v.sort_unstable();
assert_eq!(v, vec![1, 3, 5, 7, 9]);
} else if val & 1 == 1 {
nested(set.extend(val), val-1);
} else {
nested(set.fork(), val-1);
}
}
let mut set = StackCons::empty();
nested(set, 10);§SetCollection trait
collection feature locks the SetCollection trait:
(simplified)
pub trait SetCollection {
type ExtendMemory;
fn new() -> Self;
fn extend(&mut self, new_item: Self::Item) -> Self::ExtendMemory;
fn contains_ref(&self, item: &Self::Item) -> bool;
fn remove(&mut self, present_item: Self::ExtendMemory);
}Note that this trait can be pretty easily implemented for normal kind of collection, like Vec or HashSet, an that’s exactly what they implement, actually. User is not intended to use this trait directly. Instead, use CollectionSet wrapper to convert Vec, BTreeSet or HashSet into StackedSet implementation. Exported variants of CollectionSet can be found in this crate.
§StackedSet trait
If StackedSet need to be implemented, here’s a bit of explanation on Shorten:
Stacked sets are intended to borrow other instances of their type, but technically shorter lifetime makes it a different type. There’s no good way to represent that, so in case of this trait, it’s defined as CollectionSet::Shorten. Basically, Shorten should be assigned a Self type, but with a lifetime provided by associated type definition.
Another thing to keep in mind is that &mut T is invariant over lifetime, so some sort of type surgery is usually required during implementation.
Also please remember that each instance of StackedSet is responsible for removing it’s own element from the set. This is usually done via Drop implementation, but it’s not necessarily required - for example, StackCons does not need it.
Modules§
- collection
collection - Defines implementation of
StackedSetbased on normal kind of collection.
Structs§
- Stack
Cons cons Cons list-like implementation ofStackedSet
Traits§
- Stacked
Set - Common trait for stacked set implementations. Users are intended to define their input as
impl StackedSet<Item = WhateverItemTheyNeed>, so it’s up to the user to pick the implementation
Type Aliases§
- Alloc
Tree alloc-tree alloc::collections::BTreeSet-based implementation- Alloc
Vec alloc-vec alloc::vec::Vec-based implementation- StdHash
std-hash std::collections::HashSet-based implementation