TinyVec的定义为:

pub enum TinyVec<A: Array> {
  #[allow(missing_docs)]
  Inline(ArrayVec<A>),
  #[allow(missing_docs)]
  Heap(Vec<A::Item>),
}

但是,如果您运行此代码,您会得到有趣的结果(playground):

use tinyvec::*;

fn main() {
    dbg!(std::mem::size_of::<Vec<u8>>());
    
    dbg!(std::mem::size_of::<TinyVec<[u8; 13]>>());
    dbg!(std::mem::size_of::<TinyVec<[u8; 14]>>());
    dbg!(std::mem::size_of::<TinyVec<[u8; 15]>>());
    dbg!(std::mem::size_of::<TinyVec<[u8; 16]>>());
}

输出:

[src/main.rs:4] std::mem::size_of::<Vec<u8>>() = 24
[src/main.rs:6] std::mem::size_of::<TinyVec<[u8; 13]>>() = 24
[src/main.rs:7] std::mem::size_of::<TinyVec<[u8; 14]>>() = 24
[src/main.rs:8] std::mem::size_of::<TinyVec<[u8; 15]>>() = 32
[src/main.rs:9] std::mem::size_of::<TinyVec<[u8; 16]>>() = 32

我知道Rust可以使用"小众"来优化枚举大小--基本上它将判别式放在一些未使用的空间中,甚至是变量的未使用的值中.但我真的不明白它是如何做到这一点的.

肯定有一些Vec<>的配置是无效的,因此可以用于判别式,例如大小和容量、空数据指针和大小以及0等等.但Rust真的足够聪明来解决这个问题,还是它是一个手工编码的利基市场?

推荐答案

秘诀是ArrayVec<[u8; 14]>小于Vec<u8>,需要16个字节:

pub struct ArrayVec<A> {
  len: u16,
  pub(crate) data: A, // A = [u8; 14]
}

Vec的24字节相比,剩下的8个字节是TinyVec<[u8; 14]>没有使用的.这些字节为零可以表示Inline变体,而它们为非零字节可以表示Heap变体.换句话说,编译器足够智能,可以使用Vec的数据指针部分,即NonNull<T>作为the niche.

因此,TinyVec可以是:

struct TinyVec<A> {
    // ArrayVec (first 8 bytes are zero):
    this_is_null: *mut [u8; 14],
    len: u16,
    data: [u8; 14],
}

或者:

struct TinyVec<A> {
    // Vec (first 8 bytes are non-zero and point to data):
    data: *mut [u8; 14],
    len: usize,
    capacity: usize,
}

第一个字段的值是区分这两种可能性的指标.

使用此不安全代码可以观察到这一点:

type TinyVec = tinyvec::TinyVec<[u8; 14]>;

fn main() {
    assert_eq!(std::mem::size_of::<TinyVec>(), 24);
    let a: TinyVec = (0..10).collect();
    let b: TinyVec = (0..20).collect();
    unsafe {
        // XXX this makes assumptions about the layout of TinyVec and Vec
        // not guaranteed by rustc
        println!("{:?}", std::mem::transmute::<_, &(usize, u16, [u8; 14])>(&a));
        println!("{:?}", std::mem::transmute::<_, &(usize, usize, usize)>(&b));
    }
}

Playground

它打印的内容如下:

(0, 10, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0])
(94630321433040, 20, 20)

第一行显示了内联表示形式:空小生境、长度10和值array.第二行显示了堆的表示形式:非空小生境(指向堆的数据指针),后跟长度和容量.

Rust相关问答推荐

什么是Rust惯用的方式来使特征向量具有单个向量项的别名?

有没有办法模仿对象安全克隆?

为什么`Vec i64`的和不知道是`Option i64`?

亚性状上位性状上的 rust 病伴生型界限

有没有可能让泛型Rust T总是堆分配的?

写入引用会更新基础值,但引用会打印意外的值

习语选项<;T>;到选项<;U>;当T->;U用From定义

Rust 中什么时候可以返回函数生成的字符串切片&str?

这是什么:`impl Trait for T {}`?

使用占位符获取用户输入

为什么需要静态生命周期以及在处理 Rust 迭代器时如何缩小它?

如何从 rust 中的同一父目录导入文件

当你删除一个存在于堆栈中的值时,为什么 rust 不会抱怨

为什么 for_each 在释放模式(cargo run -r)下比 for 循环快得多?

在构建器模式中捕获 &str 时如何使用生命周期?

为什么我不能克隆可克隆构造函数的Vec?

如何将 Rust 中的树状 struct 展平为 Vec<&mut ...>?

是否可以在 Rust 中的特定字符上实现特征?

当 T 不是副本时,为什么取消引用 Box 不会抱怨移出共享引用?

如何使用 rust bindgen 生成的 std_vector