#fallible #map #set #stack #memory-safety #key-set #validate

no-std scapegoat

通过scapegoat树实现的安全、可错误处理、嵌入式友好的有序集合/映射。与BTreeSet/BTreeMap进行验证。

15 个稳定版本

2.3.0 2022年2月10日
2.1.0 2021年12月28日
1.7.0 2021年11月13日
1.2.0 2021年7月16日
0.1.0 2020年9月4日

#475数据结构

Download history 16/week @ 2024-04-22 20/week @ 2024-05-06 20/week @ 2024-05-13 34/week @ 2024-05-20 71/week @ 2024-05-27 64/week @ 2024-06-03 69/week @ 2024-06-10 62/week @ 2024-06-17 37/week @ 2024-06-24 33/week @ 2024-07-01 24/week @ 2024-07-08 22/week @ 2024-07-15 47/week @ 2024-07-22 16/week @ 2024-07-29 73/week @ 2024-08-05

每月158 次下载
miratope 中使用

MIT 许可证

250KB
4K SLoC


scapegoat


scapegoat

crates.io MSRV 1.55+ docs.rs GitHub Actions License: MIT Unsafe-Zero-Percent

通过基于场的scapegoat树实现的有序集合和映射数据结构(内存高效、自我平衡的二叉搜索树)。

  • 嵌入式友好:默认情况下为#![no_std]
  • 安全:包含所有依赖项的#![forbid(unsafe_code)]
  • 通过差异模糊测试进行验证,与标准库的BTreeSetBTreeMap进行比较。

关于

两个API

追求三个特性

  • 最大安全性:强大的内存安全保证,因此#![forbid(unsafe_code)]

    • 编译时安全性:没有unsafe(没有原始指针解引用等)。
    • 调试时安全性:在测试中执行的逻辑不变量使用debug_assert!
    • 运行时安全性:无内部可变性(例如,无需Rc<RefCell<T>>的运行时检查)。
  • 最小占用空间:资源使用量低,因此使用#![no_std]

    • 内存高效:节点只有子索引元数据,节点内存被重用。
    • 无递归:所有操作都是迭代的,因此栈使用量固定,运行时最小化。
    • 零复制:重建/移除就地重定向,节点永远不会被复制或克隆。
  • 可靠性:对于优先考虑鲁棒性的嵌入式用例(或内核空间代码)。

    • 每个易出错误的API(例如,insertappendextend等)都提供了一种try_*变体。
    • 内存不足(OOM) panic!变得可避免:try_*变体返回Result<_, SgError>
    • 堆碎片化不影响最坏情况执行时间(WCET),这个库不使用堆。

其他功能

  • 泛型:映射键和集合元素可以是实现OrdDefault特质的任何类型。
  • 任意可变:可以插入和删除元素,映射值可以修改。安全。

使用方法

SgMap非穷举,#![no_std] API 示例(对于std::collections::BTreeMap几乎相同)

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 static_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.try_insert(4, "borrow checker").is_ok());

// Ordered reference iterator
assert!(example
    .iter()
    .map(|(_, v)| *v)
    .collect::<ArrayVec<[&str; CAPACITY]>>()
    .iter()
    .eq(["Please","don't blame","the","borrow checker"].iter()));

// Container indexing
assert_eq!(example[&3], "the");

// Head removal
let please_tuple = example.pop_first().unwrap();
assert_eq!(please_tuple, (1, "Please"));

// By-predicate removal
example.retain(|_, v| !v.contains("a"));

// Extension
let iterable = array_vec![
    [(isize, &str); CAPACITY] =>
    (1337, "safety!"), (0, "Leverage"), (100, "for")
];
example.extend(iterable.into_iter());

// Value mutation
if let Some(three_val) = example.get_mut(&3) {
    *three_val = &mut static_str;
}

// New message :)
assert!(example
    .into_values()
    .collect::<ArrayVec<[&str; CAPACITY]>>()
    .iter()
    .eq(["Leverage","your friend the","borrow checker","for","safety!"].iter()));

更多示例在这里

堆栈容量:重要上下文

根据上述内容,const泛型类型参数决定了集合容量。因此,也决定了堆栈使用。这种使用是固定的

use core::mem::size_of_val;
use scapegoat::SgMap;

let small_map: SgMap<u64, u64, 100> = SgMap::new(); // 100 item capacity
let big_map: SgMap<u64, u64, 2_048> = SgMap::new(); // 2,048 item capacity

#[cfg(target_pointer_width = "64")]
#[cfg(not(feature = "low_mem_insert"))]
#[cfg(not(feature = "fast_rebalance"))]
{
    assert_eq!(size_of_val(&small_map), 2_680); // 2.7 KB
    assert_eq!(size_of_val(&big_map), 53_328);  // 53.3 KB
}

支持的最大容量是65_535(例如,0xffffu16::MAX)项目。请注意

  • 对于嵌入式平台,堆栈大小限制(受可用RAM限制)在制造商的数据表中指示。
  • 在Linux上,主线程的默认堆栈限制为8MB,新生的线程为2MB(除非重写)。
  • 在任意操作系统上运行cargo test,除非设置环境变量RUST_MIN_STACK,否则限制为2MB。

警告:尽管栈使用量是恒定的(无递归),但如果N(常量泛型容量)和/或存储的项目类型(泛型)过大,则在运行时可能会发生栈溢出。注意溢出与缓冲区溢出(Rust可以防止)是不同的。无论如何,您必须测试以确保您不会超过目标平台的栈大小限制。Rust只支持x86/x64上的栈探测,尽管已经提出了针对其他架构的创意链接解决方案

有关高级配置选项,请参阅此处文档

可信依赖项

此库有三个依赖项,每个依赖项都没有自己的依赖项(例如,总共有三个依赖项)。

  • tinyvec - #![no_std]#![forbid(unsafe_code)]Vec的替代方案。
  • micromath - #![no_std]#![forbid(unsafe_code)]浮点近似。
  • smallnum - #![no_std]#![forbid(unsafe_code)]整数抽象。

由于此库及其所有依赖项都#![forbid(unsafe_code)],因此不会将第三方unsafe代码引入到您的项目中。这最大限度地提高了内存安全的静态保证(通过Rust的类型系统强制执行)。除内存安全之外的鲁棒性和正确性属性通过差异模糊测试动态验证

其他注意事项

总体目标

此项目是安全、便携式数据结构设计的一个练习。目标是向嵌入式开发者提供熟悉、易于使用的API,这些系统在其他情况下没有动态集合的奢侈。不会牺牲安全性。

scapegoat的速度或成熟度不如标准库的BTreeMap/BTreeSet(通过cargo bench进行基准测试)。标准库已经针对缓存性能进行了大量优化。此库针对低、仅栈的内存占用进行了优化。它提供

  • 最佳兼容性:API 大部分是 BTreeMapBTreeSet 的子集,因此它基本上可以作为 #![no_std] 系统的“即插即用”替代品。如果您需要的 API 还未得到支持,请 提交一个问题

  • 动态验证:使用 基于覆盖率、结构感知、差异模糊测试 来证明此实现在逻辑上是等价的,并且具有相同的可靠性。

  • 可调性能:一个 单个浮点数值 优化了 insertgetremove 操作类的相对性能。并且可以在运行时更改。

算法复杂度

空间复杂度始终为 O(n)

操作 平均情况 最坏情况
get O(log n) O(log n)
insert O(log n) 平均 O(log n)
remove O(log n) 平均 O(log n)
第一个 O(1) O(1)
最后一个 O(1) O(1)

内存占用演示

  • 代码大小演示 - 调用 SgMap<usize, usize, 1024> 以及 insertgetremove:x86-64 二进制文件的 14.2KB。注意:您可能需要使用超过 3 个函数,这将导致更多可执行代码被包含在内。

  • 堆栈空间演示 - SgMap<u8, u8, 128>:存储成本为 1.3KB。注意:运行时需要更多的堆栈空间进行维护(例如,重新平衡)。

许可和贡献

遵循 MIT 许可协议。欢迎 贡献

依赖项

约 510KB
约 16K SLoC