{"id":13632547,"url":"https://github.com/tnballo/scapegoat","last_synced_at":"2025-04-04T22:07:28.453Z","repository":{"id":44295401,"uuid":"290332708","full_name":"tnballo/scapegoat","owner":"tnballo","description":"Safe, fallible, embedded-friendly ordered set/map via a scapegoat tree. Validated against BTreeSet/BTreeMap.","archived":false,"fork":false,"pushed_at":"2022-02-10T19:03:03.000Z","size":1644,"stargazers_count":259,"open_issues_count":4,"forks_count":15,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-01T17:17:51.601Z","etag":null,"topics":["binary-search-tree","data-structures","embedded","no-std","rust","scapegoat-tree"],"latest_commit_sha":null,"homepage":"https://docs.rs/scapegoat","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tnballo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-25T22:00:07.000Z","updated_at":"2025-03-11T17:44:36.000Z","dependencies_parsed_at":"2022-09-26T20:31:45.227Z","dependency_job_id":null,"html_url":"https://github.com/tnballo/scapegoat","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnballo%2Fscapegoat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnballo%2Fscapegoat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnballo%2Fscapegoat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnballo%2Fscapegoat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tnballo","download_url":"https://codeload.github.com/tnballo/scapegoat/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247256112,"owners_count":20909240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binary-search-tree","data-structures","embedded","no-std","rust","scapegoat-tree"],"created_at":"2024-08-01T22:03:06.536Z","updated_at":"2025-04-04T22:07:28.434Z","avatar_url":"https://github.com/tnballo.png","language":"Rust","funding_links":[],"categories":["Rust","Libraries","库 Libraries"],"sub_categories":["Data structures","数据结构 Data structures"],"readme":"\u003cbr\u003e\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/tnballo/scapegoat/master/img/scapegoat.svg\" width=\"333\" alt=\"scapegoat\"\u003e\u003c/p\u003e\u003cbr\u003e\n# scapegoat\n\n[![crates.io](https://img.shields.io/crates/v/scapegoat.svg)](https://crates.io/crates/scapegoat)\n![MSRV 1.55+](https://img.shields.io/badge/rustc-1.55+-yellow.svg)\n[![docs.rs](https://docs.rs/scapegoat/badge.svg)](https://docs.rs/scapegoat/)\n[![GitHub Actions](https://github.com/tnballo/scapegoat/workflows/test/badge.svg)](https://github.com/tnballo/scapegoat/actions)\n[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/tnballo/scapegoat/blob/master/LICENSE)\n[![Unsafe-Zero-Percent](https://img.shields.io/badge/Unsafety-0%25-brightgreen.svg)](https://github.com/tnballo/scapegoat/blob/master/src/lib.rs#L199)\n\nOrdered set and map data structures via an arena-based [scapegoat tree](https://people.csail.mit.edu/rivest/pubs/GR93.pdf) (memory-efficient, self-balancing binary search tree).\n\n* Embedded-friendly: `#![no_std]` by default.\n* Safe: `#![forbid(unsafe_code)]`, including all dependencies.\n* Validated via [differential fuzzing](https://tiemoko.com/blog/diff-fuzz/), against the standard library's `BTreeSet` and `BTreeMap`.\n\n### About\n\nTwo APIs:\n\n* Ordered Set API ([`SgSet`](crate::SgSet)) - subset of [`BTreeSet`](https://doc.rust-lang.org/std/collections/struct.BTreeSet.html) nightly.\n* Ordered Map API ([`SgMap`](crate::SgMap)) - subset of [`BTreeMap`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html) nightly.\n\nStrives for three properties:\n\n* **Maximal safety:** strong [memory safety](https://tiemoko.com/blog/blue-team-rust/) guarantees, hence `#![forbid(unsafe_code)]`.\n    * **Compile-time safety:** no `unsafe` (no raw pointer dereference, etc.).\n    * **Debug-time safety:** `debug_assert!` for logical invariants exercised in testing.\n    * **Runtime safety:** no interior mutability (e.g. no need for `Rc\u003cRefCell\u003cT\u003e\u003e`'s runtime check).\n\n* **Minimal footprint:** low resource use, hence `#![no_std]`.\n    * **Memory-efficient:** nodes have only child index metadata, node memory is re-used.\n    * **Recursion-free:** all operations are iterative, so stack use is fixed and runtime is minimized.\n    * **Zero-copy:** rebuild/removal re-point in-place, nodes are never copied or cloned.\n\n* **Fallibility**: for embedded use cases prioritizing robustness (or [kernelspace](https://lkml.org/lkml/2021/4/14/1099) code).\n    * A `try_*` variant of each fallible API (e.g. `insert`, `append`, `extend`, etc.) is available.\n    * **Out-Of-Memory (OOM)** `panic!` becomes avoidable: `try_*` variants return [`Result\u003c_, SgError\u003e`](crate::SgError).\n    * Heap fragmentation doesn't impact **Worst Case Execution Time (WCET)**, this library doesn't use the heap.\n\nOther features:\n\n* **Generic:** map keys and set elements can be any type that implements traits [`Ord`](https://doc.rust-lang.org/std/cmp/trait.Ord.html) and [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html).\n* **Arbitrarily mutable:** elements can be inserted and removed, map values can be mutated. Safely.\n\n### Usage\n\n`SgMap` non-exhaustive, `#![no_std]` API example (would work almost identically for `std::collections::BTreeMap`):\n\n```rust\nuse scapegoat::SgMap;\nuse tinyvec::{array_vec, ArrayVec};\n\n// This const is an argument to each generic constructor below.\n// So we'll use *only the bare minimum* memory for 5 elements.\n// - Stack RAM usage can be precisely controlled: per map instance (constructor call-site).\n// - To save executable RAM/ROM (monomorphization!), stick to a global capacity like this.\nconst CAPACITY: usize = 5;\n\nlet mut example = SgMap::\u003c_, _, CAPACITY\u003e::new(); // BTreeMap::new()\nlet mut static_str = \"your friend the\";\n\n// Insert \"dynamically\" (as if heap)\nexample.insert(3, \"the\");\nexample.insert(2, \"don't blame\");\nexample.insert(1, \"Please\");\n\n// Fallible insert variant\nassert!(example.try_insert(4, \"borrow checker\").is_ok());\n\n// Ordered reference iterator\nassert!(example\n    .iter()\n    .map(|(_, v)| *v)\n    .collect::\u003cArrayVec\u003c[\u0026str; CAPACITY]\u003e\u003e()\n    .iter()\n    .eq([\"Please\",\"don't blame\",\"the\",\"borrow checker\"].iter()));\n\n// Container indexing\nassert_eq!(example[\u00263], \"the\");\n\n// Head removal\nlet please_tuple = example.pop_first().unwrap();\nassert_eq!(please_tuple, (1, \"Please\"));\n\n// By-predicate removal\nexample.retain(|_, v| !v.contains(\"a\"));\n\n// Extension\nlet iterable = array_vec![\n    [(isize, \u0026str); CAPACITY] =\u003e\n    (1337, \"safety!\"), (0, \"Leverage\"), (100, \"for\")\n];\nexample.extend(iterable.into_iter());\n\n// Value mutation\nif let Some(three_val) = example.get_mut(\u00263) {\n    *three_val = \u0026mut static_str;\n}\n\n// New message :)\nassert!(example\n    .into_values()\n    .collect::\u003cArrayVec\u003c[\u0026str; CAPACITY]\u003e\u003e()\n    .iter()\n    .eq([\"Leverage\",\"your friend the\",\"borrow checker\",\"for\",\"safety!\"].iter()));\n```\n\nAdditional [examples here](https://github.com/tnballo/scapegoat/blob/master/examples/README.md).\n\n### Stack Capacity: Important Context\n\nPer the above, const generic type parameters decide collection capacity.\nAnd thus also stack usage.\nThat usage is fixed:\n\n```rust\nuse core::mem::size_of_val;\nuse scapegoat::SgMap;\n\nlet small_map: SgMap\u003cu64, u64, 100\u003e = SgMap::new(); // 100 item capacity\nlet big_map: SgMap\u003cu64, u64, 2_048\u003e = SgMap::new(); // 2,048 item capacity\n\n#[cfg(target_pointer_width = \"64\")]\n#[cfg(not(feature = \"low_mem_insert\"))]\n#[cfg(not(feature = \"fast_rebalance\"))]\n{\n    assert_eq!(size_of_val(\u0026small_map), 2_680); // 2.7 KB\n    assert_eq!(size_of_val(\u0026big_map), 53_328);  // 53.3 KB\n}\n```\n\nThe maximum supported capacity is `65_535` (e.g. `0xffff` or [`u16::MAX`](https://doc.rust-lang.org/std/primitive.u16.html#associatedconstant.MAX)) items.\nPlease note:\n\n* For embedded platforms, stack size limit (bound by available RAM) is indicated in the manufacturer's datasheet.\n* On Linux, the default stack limit is 8MB for the main thread and 2MB for spawned threads (unless [overwritten](https://doc.rust-lang.org/std/thread/struct.Builder.html#method.stack_size)).\n* Running `cargo test` on any OS, 2MB is the limit unless the environment variable [`RUST_MIN_STACK`](https://doc.rust-lang.org/std/thread/index.html#stack-size) is set.\n\n\n\u003e **WARNING:**\n\u003e Although stack usage is constant (no recursion), a stack overflow can happen at runtime if `N` (const generic capacity) and/or the stored item type (generic) is too large.\n\u003e Note *stack* overflow is distinct from *buffer* overflow (which safe Rust prevents).\n\u003e Regardless, you must test to ensure you don't exceed the stack size limit of your target platform.\n\u003e Rust only supports stack probes on x86/x64, although [creative linking solutions](https://blog.japaric.io/stack-overflow-protection/) have been suggested for other architectures.\n\nFor advanced configuration options, please see [the documentation here](https://github.com/tnballo/scapegoat/blob/master/CONFIG.md).\n\n### Trusted Dependencies\n\nThis library has three dependencies, each of which have no dependencies of their own (e.g. exactly three total dependencies).\n\n* [`tinyvec`](https://crates.io/crates/tinyvec) - `#![no_std]`, `#![forbid(unsafe_code)]` alternative to `Vec`.\n* [`micromath`](https://crates.io/crates/micromath) - `#![no_std]`, `#![forbid(unsafe_code)]` floating point approximations.\n* [`smallnum`](https://crates.io/crates/smallnum) - `#![no_std]`, `#![forbid(unsafe_code)]` integer abstraction.\n\nBecause this library and all dependencies are `#![forbid(unsafe_code)]`, no 3rd-party `unsafe` code is introduced into your project.\nThis maximizes **static guarantees** for memory safety (enforced via Rust's type system).\nRobustness and correctness properties beyond memory safety are **validated dynamically**, via differential fuzzing.\n\n### Additional Considerations\n\n**General Goals**\n\nThis project is an exercise in safe, portable data structure design.\nThe goal is to offer embedded developers familiar, ergonomic APIs on resource constrained systems that otherwise don't get the luxury of dynamic collections.\nWithout sacrificing safety.\n\n`scapegoat` is not as fast or mature as the [standard library's `BTreeMap`/`BTreeSet`](http://cglab.ca/~abeinges/blah/rust-btree-case/) (benchmarks via `cargo bench`).\nThe standard library has been heavily optimized for cache performance.\nThis library is optimized for low, stack-only memory footprint.\nIt offers:\n\n* **Best-effort Compatibility:** APIs are mostly a subset of `BTreeMap`'s/`BTreeSet`'s, making it a mostly \"drop-in\" replacement for `#![no_std]` systems. Please [open an issue](https://github.com/tnballo/scapegoat/issues) if an API you need isn't yet supported.\n\n* **Dynamic Validation:** [Coverage-guided, structure-aware, differential fuzzing](https://github.com/tnballo/scapegoat/tree/master/fuzz) is used to demonstrate that this implementation is logically equivalent and equally reliable.\n\n* **Tunable Performance:** A [single floating point value](https://github.com/tnballo/scapegoat/blob/master/CONFIG.md#tuning-the-the-trees-a-factor) optimizes relative performance of `insert`, `get`, and `remove` operation classes. And it can be changed at runtime.\n\n**Algorithmic Complexity**\n\nSpace complexity is always `O(n)`. Time complexity:\n\n| Operation | Average Case | Worst Case |\n| --- | --- | --- |\n| `get` | `O(log n)` | `O(log n)` |\n| `insert` | `O(log n)` | Amortized `O(log n)` |\n| `remove` | `O(log n)` | Amortized `O(log n)` |\n| `first` | `O(1)` | `O(1)` |\n| `last` | `O(1)` | `O(1)` |\n\n**Memory Footprint Demos**\n\n* [Code size demo](https://github.com/tnballo/scapegoat/blob/master/misc/min_size/README.md) - `SgMap\u003cusize, usize, 1024\u003e` 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.\n\n* [Stack space demo](https://github.com/tnballo/scapegoat/blob/master/examples/tiny_map.rs) - `SgMap\u003cu8, u8, 128\u003e`: **1.3KB** storage cost. Caveat: more stack space is required for runtime book keeping (e.g. rebalancing).\n\n### License and Contributing\n\nLicensed under the [MIT license](https://github.com/tnballo/scapegoat/blob/master/LICENSE).\n[Contributions](https://github.com/tnballo/scapegoat/blob/master/CONTRIBUTING.md) are welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftnballo%2Fscapegoat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftnballo%2Fscapegoat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftnballo%2Fscapegoat/lists"}