16个版本
0.2.13 | 2023年12月3日 |
---|---|
0.2.12 | 2023年2月20日 |
0.2.10 | 2022年12月5日 |
0.2.9 | 2022年11月2日 |
0.1.0 | 2018年5月15日 |
#48 in 数据结构
209,248每月下载量
用于 190 个crate(17直接)
140KB
2K SLoC
thin-vec
ThinVec是一个将长度和容量内联存储的Vec,使其占用更少的空间。
目前这个crate主要存在是为了方便Gecko(Firefox)FFI,但它也可以作为一个本地的Rust库很好地工作。
lib.rs
:
ThinVec
与Vec
完全相同,只是它在其分配的缓冲区中存储其len
和capacity
。
这使得薄向量的内存占用更低;特别是为不存在ThinVec<T>
预留空间的情况下。因此,Vec<ThinVec<T>>
和Option<ThinVec<T>>::None
将浪费更少的空间。指针大小也意味着它可以传递/存储在寄存器中。
当然,任何实际构造的ThinVec
理论上会有更大的分配,但分配器的模糊性质意味着这实际上可能并不成立。
保留的Vec
属性
ThinVec::new()
不会分配(它指向一个静态分配的单例)- 重新分配可以在原地完成
size_of::<ThinVec<T>>()
==size_of::<Option<ThinVec<T>>>()
Vec
的属性无法保留
ThinVec<T>
无法以零成本往返转换为Box<[T]>
、String
或*mut T
from_raw_parts
不存在ThinVec
目前不会为空大小类型(例如ThinVec<()>
)进行非分配,但如果有人足够关心并实现它,则可以做到。
Gecko FFI
如果您启用 gecko-ffi 功能,ThinVec
将直接与 Gecko(Firefox)中的 nsTArray 类型桥接。也就是说,ThinVec
和 nsTArray 具有相同的布局 但不是 ABIs,因此 nsTArrays/ThinVecs 可以由 C++ 和 Rust 本地操作,并且可以在 FFI 边界转移所有权(注意下面的警告!)。
虽然这个功能很方便,但使用它也是固有的危险,因为 Rust 和 C++ 互不了解。具体来说,这可能会与非 POD 类型(具有析构函数、移动构造函数或 !Copy
)有关的问题。
不要按值传递
最重要的是要记住,FFI 函数不能按值传递 ThinVec/nsTArray。也就是说,这些是损坏的 API
// BAD WRONG
extern fn process_data(data: ThinVec<u32>) { ... }
// BAD WRONG
extern fn get_data() -> ThinVec<u32> { ... }
您必须改为按引用传递
// Read-only access, ok!
extern fn process_data(data: &ThinVec<u32>) {
for val in data {
println!("{}", val);
}
}
// Replace with empty instance to take ownership, ok!
extern fn consume_data(data: &mut ThinVec<u32>) {
let owned = mem::replace(data, ThinVec::new());
mem::drop(owned);
}
// Mutate input, ok!
extern fn add_data(dataset: &mut ThinVec<u32>) {
dataset.push(37);
dataset.push(12);
}
// Return via out-param, usually ok!
//
// WARNING: output must be initialized! (Empty nsTArrays are free, so just do it!)
extern fn get_data(output: &mut ThinVec<u32>) {
*output = thin_vec![1, 2, 3, 4, 5];
}
对于那些真正想知道为什么的人的忽略解释
基本问题是 Rust 和 C++ 目前无法就析构函数进行通信,而 C++ 的语义要求在函数返回时运行函数参数的析构函数。这是调用者还是被调用者的责任也是平台特定的,因此尝试手动解决这个问题将是混乱的。
此外,具有析构函数的类型会改变其 C++ ABI,因为该类型必须在内存中实际存在(与通常在寄存器中传递的简单结构不同)。我们目前没有一种方法将此情况通知 Rust,因此即使我们解决了与 MaybeUninit 有关的析构函数问题,如果没有一些 RFC 来添加显式的 rustc 支持,它仍然不会起作用。
从现实的角度来看,最好的解决方案是有一个“更重”的 bindgen,它可以秘密生成 FFI 粘合剂,以便我们可以“按值”传递事物,并且它在我们的背后生成按引用代码(就像 cxx crate 所做的那样)。但这会模糊调试/searchfox。
类型应该是简单可重定位的
在Rust中,类型总是可以简单地进行重定位(除非适当地借用/固定/隐藏)。这意味着所有Rust类型都可以通过位拷贝进行重定位,你不能提供在发生这种情况时执行的拷贝或移动构造函数,并且旧位置不会运行其析构函数。这将为具有显著位置的类型(侵入式指向自身或其位置已注册到服务的类型)带来问题。
虽然如果你非常小心,重定位通常是可预测的,但你应该**避免在Rust FFI中使用具有显著位置的类型**。
具体来说,ThinVec
在需要重新分配缓冲区以更改其容量时,会简单地重定位其内容。这是nsTArray的默认重新分配策略,适用于大多数类型。只是要注意这个限制!
自动数组很危险
ThinVec
对处理存储在栈上的缓冲区的自动数组有一些支持,但这并不是经过充分测试的。
无论我们提供多少支持,Rust都不会意识到缓冲区有限的生命周期,因此标准自动数组安全警告也适用关于返回/存储它们!ThinVec
永远不会自己生成自动数组,所以这只是一个将nsTArray传输到Rust时的问题。
其他问题
标准的FFI警告也适用
- Rust对POD类型的初始化要求更加严格(如果必须使用,请使用MaybeUninit)
ThinVec<T>
不知道C++版本的T
是否有移动/拷贝/赋值/删除重载nsTArray<T>
不知道Rust版本的T
是否有Drop/Clone实现- C++可以做一些Rust无法捕获的不稳定操作
- C++和Rust对零大小/空类型应该如何处理没有达成一致
如果你没有链接到已定义nsTArray的代码,gecko-ffi功能将不会工作。具体来说,我们必须共享nsTArray空单例的符号。如果没有定义,你将得到链接错误。
gecko-ffi功能还将ThinVec
限制为nsTArray的旧行为。最值得注意的是,nsTArray的最大容量为i32::MAX(约21亿项)。可能不是问题。可能是。
依赖关系
~175KB