Ordered set and map data structures via an arena-based scapegoat tree (memory-efficient, self-balancing binary search tree).
#![no_std]
by default.#![forbid(unsafe_code)]
, including all dependencies.BTreeSet
and BTreeMap
.Two APIs:
SgSet
) - subset of BTreeSet
nightly.SgMap
) - subset of BTreeMap
nightly.Strives for three properties:
Maximal safety: strong memory safety guarantees, hence #![forbid(unsafe_code)]
.
unsafe
(no raw pointer dereference, etc.).debug_assert!
for logical invariants exercised in testing.Rc<RefCell<T>>
's runtime check).Minimal footprint: low resource use, hence #![no_std]
.
Fallibility: for embedded use cases prioritizing robustness (or kernelspace code).
try_*
variant of each fallible API (e.g. insert
, append
, extend
, etc.) is available.panic!
becomes avoidable: try_*
variants return Result<_, SgError>
.Other features:
Ord
and Default
.SgMap
non-exhaustive, #![no_std]
API example (would work almost identically for std::collections::BTreeMap
):
```rust use scapegoat::SgMap; use tinyvec::{array_vec, ArrayVec};
// This const is an argument to each generic constructor below. // So we'll use only the bare minimum memory for 5 elements. // - Stack RAM usage can be precisely controlled: per map instance (constructor call-site). // - To save executable RAM/ROM (monomorphization!), stick to a global capacity like this. const CAPACITY: usize = 5;
let mut example = SgMap::<_, _, CAPACITY>::new(); // BTreeMap::new() let mut stack_str = "your friend the";
// Insert "dynamically" (as if heap) example.insert(3, "the"); example.insert(2, "don't blame"); example.insert(1, "Please");
// Fallible insert variant assert!(example.tryinsert(4, "borrow checker").isok());
// Ordered reference iterator
assert!(example
.iter()
.map(|(_, v)| *v)
.collect:: // Container indexing
assert_eq!(example[&3], "the"); // Head removal
let pleasetuple = example.popfirst().unwrap();
asserteq!(pleasetuple, (1, "Please")); // By-predicate removal
example.retain(|_, v| !v.contains("a")); // Extension
let iterable = arrayvec![
[(isize, &str); CAPACITY] =>
(1337, "safety!"), (0, "Leverage"), (100, "for")
];
example.extend(iterable.intoiter()); // Value mutation
if let Some(threeval) = example.getmut(&3) {
*threeval = &mut stackstr;
} // New message :)
assert!(example
.into_values()
.collect:: Additional examples here. Per the above, const generic type parameters decide collection capacity.
And thus also stack usage.
That usage is fixed: ```rust
use core::mem::sizeofval;
use scapegoat::SgMap; let smallmap: SgMap {
asserteq!(sizeofval(&smallmap), 2680); // 2.7 KB
asserteq!(sizeofval(&bigmap), 53328); // 53.3 KB
}
``` The maximum supported capacity is WARNING:
Although stack usage is constant (no recursion), a stack overflow can happen at runtime if For advanced configuration options, please see the documentation here. This library has three dependencies, each of which have no dependencies of their own (e.g. exactly three total dependencies). Because this library and all dependencies are General Goals This project is an exercise in safe, portable data structure design.
The goal is to offer embedded developers familiar, ergonomic APIs on resource constrained systems that otherwise don't get the luxury of dynamic collections.
Without sacrificing safety. Best-effort Compatibility: APIs are mostly a subset of Dynamic Validation: Coverage-guided, structure-aware, differential fuzzing is used to demonstrate that this implementation is logically equivalent and equally reliable. Tunable Performance: A single floating point value optimizes relative performance of Algorithmic Complexity Space complexity is always | Operation | Average Case | Worst Case |
| --- | --- | --- |
| Memory Footprint Demos Code size demo - Stack space demo - Licensed under the MIT license.
Contributions are welcome!Stack Capacity: Important Context
[cfg(targetpointerwidth = "64")]
[cfg(not(feature = "lowmeminsert"))]
[cfg(not(feature = "fast_rebalance"))]
65_535
(e.g. 0xffff
or u16::MAX
) items.
Please note:
cargo test
on any OS, 2MB is the limit unless the environment variable RUST_MIN_STACK
is set.
N
(const generic capacity) and/or the stored item type (generic) is too large.
Note stack overflow is distinct from buffer overflow (which safe Rust prevents).
Regardless, you must test to ensure you don't exceed the stack size limit of your target platform.
Rust only supports stack probes on x86/x64, although creative linking solutions have been suggested for other architectures.Trusted Dependencies
tinyvec
- #![no_std]
, #![forbid(unsafe_code)]
alternative to Vec
.micromath
- #![no_std]
, #![forbid(unsafe_code)]
floating point approximations.smallnum
- #![no_std]
, #![forbid(unsafe_code)]
integer abstraction.#![forbid(unsafe_code)]
, no 3rd-party unsafe
code is introduced into your project.
This maximizes static guarantees for memory safety (enforced via Rust's type system).
Robustness and correctness properties beyond memory safety are validated dynamically, via differential fuzzing.Additional Considerations
scapegoat
is not as fast or mature as the standard library's BTreeMap
/BTreeSet
(benchmarks via cargo bench
).
The standard library has been heavily optimized for cache performance.
This library is optimized for low, stack-only memory footprint.
It offers:
BTreeMap
's/BTreeSet
's, making it a mostly "drop-in" replacement for #![no_std]
systems. Please open an issue if an API you need isn't yet supported.insert
, get
, and remove
operation classes. And it can be changed at runtime.O(n)
. Time complexity:get
| O(log n)
| O(log n)
|
| insert
| O(log n)
| Amortized O(log n)
|
| remove
| O(log n)
| Amortized O(log n)
|
| first
| O(1)
| O(1)
|
| last
| O(1)
| O(1)
|
SgMap<usize, usize, 1024>
with insert
, get
, and remove
called: 14.2KB for an x86-64 binary. Caveat: you'll likely want to use more than 3 functions, resulting in more executable code getting included.SgMap<u8, u8, 128>
: 1.3KB storage cost. Caveat: more stack space is required for runtime book keeping (e.g. rebalancing).License and Contributing