scapegoat
Ordered set and map data structures via an arena-based scapegoat tree (memory-efficient, self-balancing binary search tree).
This library is #![no_std] compatible by default, strictly #![forbid(unsafe_code)], and verified using differential fuzzing.
About
Three APIs:
Strives for two properties:
-
Maximal safety: strong memory safety guarantees.
- Compile-time safety: no
unsafe(no raw pointer dereference, etc.). - Debug-time safety:
debug_assert!for logical invariants exercised in testing. - Runtime safety: no interior mutability (e.g. no need for
Rc<RefCell<T>>'s runtime check).
- Compile-time safety: no
-
Minimal footprint: small binary with low resource use.
- Memory-efficient: nodes have only child index metadata, node memory is re-used.
- Recursion-free: all operations are iterative, so stack use and runtime are both minimized.
- Zero-copy: rebuild/removal re-point in-place, nodes are never copied or cloned.
Other features:
- Generic: map keys and set elements can be any type that implements the
Ordtrait. - Arbitrarily mutable: elements can be insert and removed, map values can be mutated.
Usage
SGMap non-exhaustive API example (would work identically for std::collections::BTreeMap):
use SGMap;
let mut example = new;
example.insert;
example.insert;
example.insert;
example.insert;
assert_eq!;
assert_eq!;
let please_tuple = example.pop_first.unwrap;
assert_eq!;
example.insert;
let dont_blame = example.get_mut.unwrap;
dont_blame.remove;
dont_blame.insert;
assert_eq!;
Configuring a Stack Storage Limit
The maximum number of stack-stored elements (set) or key-value pairs (map/tree) is determined at compile-time, via the environment variable SG_MAX_STACK_ELEMS.
Valid values are in the range [0, 32] and powers of 2 up to 2048.
For example, to store up to 1024 items on the stack:
Please note:
-
If the
SG_MAX_STACK_ELEMSenvironment variable is not set, it will default to2048. -
For any system with dynamic (heap) memory: the first
SG_MAX_STACK_ELEMSelements are stack-allocated and the remainder will be automatically heap-allocated. -
For embedded systems without dynamic memory:
SG_MAX_STACK_ELEMSis a hard maximum - attempting to insert beyond this limit will cause a panic.- Use feature
high_assuranceto ensure error handling and avoid panic (see below).
- Use feature
The high_assurance Feature
For embedded use cases prioritizing robustness, enabling the high_assurance feature makes two changes:
-
Front-end, API Tweak:
insertandappendAPIs now returnResult.Errindicates stack storage is already at maximum capacity, so caller must handle. No heap use, no panic potential on insert. -
Back-end, Integer Packing: Because the fixed/max size of the stack arena is known, indexing integers (metadata stored at every node!) can be size-optimized. This memory micro-optimization honors the original design goals of the scapegoat data structure.
That second change is a subtle but interesting one. Example of packing saving 53% (but in reality only 61 KB) of RAM usage:
use SGMap;
use size_of;
// If you're on a 64-bit system, you can compile-time check the below numbers yourself!
// Just do:
//
// $ cargo test --doc
// $ cargo test --doc --features="high_assurance"
//
// One command per set of `cfg` macros below.
// Internally, this compile time struct packing is done with the `smallnum` crate:
// https://crates.io/crates/smallnum
// This code assumes `SG_MAX_STACK_ELEMS == 2048` (default)
let temp: = new;
assert!;
// Without packing
// With packing
Trusted Dependencies
This library has three dependencies, each of which have no dependencies of their own (e.g. exactly three total dependencies).
smallvec-!#[no_std]compatibleVecalternative. Used in Mozilla's Servo browser engine.micromath-!#[no_std],#![forbid(unsafe_code)]floating point approximations.smallnum-!#[no_std],#![forbid(unsafe_code)]integer packing.
Considerations
This project is an exercise in safe data structure design.
It's not as mature, fast, or memory efficient as the standard library's BTreeMap/BTreeSet.
It does, however, offer:
-
Best-effort Compatibility: APIs are a subset of
BTreeMap's/BTreeSet's, making it a somewhat "drop-in" replacement for!#[no_std]systems. Please open an issue if an API you need isn't yet supported! -
Dynamic Verification: Coverage-guided differential fuzzing is used to verify that this implementation is logically equivalent and equally reliable.