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 数据结构

Download history 49555/week @ 2024-04-21 48875/week @ 2024-04-28 46260/week @ 2024-05-05 47884/week @ 2024-05-12 44693/week @ 2024-05-19 42979/week @ 2024-05-26 49735/week @ 2024-06-02 46653/week @ 2024-06-09 48764/week @ 2024-06-16 53474/week @ 2024-06-23 45126/week @ 2024-06-30 50857/week @ 2024-07-07 51173/week @ 2024-07-14 50164/week @ 2024-07-21 50296/week @ 2024-07-28 53258/week @ 2024-08-04

209,248每月下载量
用于 190 个crate(17直接)

MIT/Apache

140KB
2K SLoC

Rust CI crates.io

thin-vec

ThinVec是一个将长度和容量内联存储的Vec,使其占用更少的空间。

目前这个crate主要存在是为了方便Gecko(Firefox)FFI,但它也可以作为一个本地的Rust库很好地工作。


lib.rs:

ThinVecVec完全相同,只是它在其分配的缓冲区中存储其lencapacity

这使得薄向量的内存占用更低;特别是为不存在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