【译】Rust中的Sizedness (五)

461 阅读4分钟

原文链接: https://github.com/pretzelhammer/rust-blog/blob/master/posts/sizedness-in-rust.md

原文标题: Sizedness in Rust

公众号: Rust碎碎念

用户定义的不确定大小类型

struct Unsized {
    unsized_field: [i32],
}

我们可以通过给结构体添加一个不确定大小(unsized)的字段来定义一个不确定大小结构体。不确定大小结构体只能有1个不确定大小字段并且该字段必须是这个结构体里的最后一个字段。这是第一个必要条件,以便于编译器在编译时确定每个字段在结构体中的起始偏移量,这对于高效快速的字段访问是必要的。而且,使用一个双宽度(double-width)指针最多可以追踪一个不确定大小字段,因为更多的不确定大小字段会需要更多的宽度。

那我们怎么来实例化这个结构体呢?和处理其他不确定大小类型的方式一样:首先构造一个确定性大小的版本,然后把它强制转换为不确定大小的版本。尽管如此,根据定义,Unsized总是不确定大小的,没有办法构造一个它的确定性大小版本。唯一的解决方法是把这个结构体变成泛型(generic)的,这样它就可以存在于确定性大小和不确定性大小的版本里。

struct MaybeSized<T: ?Sized> {
    maybe_sized: T,
}

fn main() {
    // unsized coercion from MaybeSized<[i32; 3]to MaybeSized<[i32]>
    let ms: &MaybeSized<[i32]> = &MaybeSized { maybe_sized: [123] };
}

所以这个的使用场景是什么?没有任何特别引人注目的特性,用户定义的不确定大小类型目前是一个相当不成熟的特性,它们的局限性超过了所能带来的益处。这里提到它纯粹了是为了内容的全面性。

有趣的事实:std::ffi::OsStrstd::path::Path是标准库里的两个不确定大小结构体,你之前可能已经使用过他们但是并没有意识到。

关键点(Key Takeaway)

  • 用户定义的不确定大小类型是一个目前尚不成熟的特性并且它们的局限性超过了所能带来的益处。

零大小类型(Zero-Sized Types)

ZST一开始听起来感觉很奇怪,但是几乎到处都在使用它们。(译者注: ZST即Zero-Sized Type的缩写)

单元类型(Unit Type)

最常见的ZST是是单元类型(也叫空元组):(),所有的空块{}的计算结果为(),并且,如果这个代码块是非空但是使用一个分号来丢弃最后的表达式,计算的结果也是(), 例如:

fn main() {
    let a: () = {};
    let b: i32 = {
        5
    };
    let c: () = {
        5;
    };
}

没有明确返回类型的函数默认都返回():

// with sugar
fn function() {}

// desugared
fn function() -> () {}

因为()是零字节,所以()的实例都是相同的,这就使得DefaultPartialEqOrd的实现变得相当简单:

use std::cmp::Ordering;

impl Default for () {
    fn default() {}
}

impl PartialEq for () {
    fn eq(&self, _other: &()) -> bool {
        true
    }
    fn ne(&self, _other: &()) -> bool {
        false
    }
}

impl Ord for () {
    fn cmp(&self, _other: &()) -> Ordering {
        Ordering::Equal
    }
}

编译器理解()是零大小类型并且会优化和()实例有关的交互。例如:一个Vec<()>永远不会执行堆分配,从Vec里推进(push)和弹出(pop)()只是对它里面的len字段进行增加或减少。

fn main() {
    // zero capacity is all the capacity we need to "store" infinitely many ()
    let mut vec: Vec<()> = Vec::with_capacity(0);
    // causes no heap allocations or vec capacity changes
    vec.push(()); // len++
    vec.push(()); // len++
    vec.push(()); // len++
    vec.pop(); // len--
    assert_eq!(2, vec.len());
}

上面的例子没有实际的应用,但是是否存一些解决方案,能够让我们以一种有意义的方式充分利用上面的思想?是的,我们可以通过把HashMap<Key, Value>Value设置为()来得到一个更高效的HashSet<Key>实现,这确实也是Rust标准库里HashSet里的实现方式:

// std::collections::HashSet
pub struct HashSet<T> {
    map: HashMap<T, ()>,
}

关键点(Key Takeaway)

  • 所有ZST的实例都是相等的
  • Rust编译器会去优化和ZST相关的操作

本文禁止转载,谢谢配合!欢迎关注我的微信公众号: Rust碎碎念

Rust碎碎念
Rust碎碎念