如何在不复制内容的情况下对实现 Deref 的类型(例如 Box)中的值进行模式匹配?

How to pattern match on values inside a type implementing Deref, such as Box, without copying the contents?

我的数据包含在 Box 中,我想对其进行模式匹配而不意外地将 Box 的内容从堆复制到堆栈;我该怎么做?

让我们假设以下代码:

enum SomeEnum {
    SomeEntry,
    AnotherEntry,
}

fn main() {
    let boxed_value = Box::new(SomeEnum::AnotherEntry);

    match *boxed_value {
        SomeEnum::SomeEntry => {}
        SomeEnum::AnotherEntry => {}
    }
}

这是将开箱即用的枚举复制到堆栈上并对该副本进行模式匹配,还是直接对盒子指向的值进行匹配?

这个变体怎么样?

use std::ops::Deref;

enum SomeEnum {
    SomeEntry,
    AnotherEntry,
}

fn main() {
    let boxed_value = Box::new(SomeEnum::AnotherEntry);

    match boxed_value.deref() {
        SomeEnum::SomeEntry => {}
        SomeEnum::AnotherEntry => {}
    }
}

似乎简单地取消引用一个框并不会自动创建一个副本,否则将无法使用 let x = &*boxed_value 创建对包含值的引用。这导致有关此语法的问题:

enum SomeEnum {
    SomeEntry,
    AnotherEntry,
}

fn main() {
    let boxed_value = Box::new(SomeEnum::AnotherEntry);

    match &*boxed_value {
        SomeEnum::SomeEntry => {}
        SomeEnum::AnotherEntry => {}
    }
}

首先:在 Rust 中,没有隐式成本高的副本,这与 C++ 不同。而在 C++ 中,默认操作是“深度复制”(通过复制构造函数或类似方法),而 Rust 中的默认操作是移动。移动是一个浅拷贝,(a) 通常非常小且便宜,(b) 在大多数情况下可以被优化器删除。要在 Rust 中获得深度克隆,您必须手动使用 .clone()。如果你不这样做,你通常不必担心这个

Second:对枚举的匹配仅查看该枚举的 discriminant(除非您绑定枚举字段,请参见下文)。那是指定枚举的哪个变体存储在值中的“标签”或“元数据”。该标签很小:几乎在所有情况下它都适合 8 位(具有超过 256 个变体的枚举很少见)。所以你不必为此担心。在您的情况下,我们有一个没有任何字段的类似 C 的枚举。所以枚举只存储标签,因此也很小。

那么复制可能代价高昂的枚举字段呢?像这样:

enum SomeEnum {
    SomeEntry(String),
    AnotherEntry,
}

let boxed_value = Box::new(SomeEnum::AnotherEntry);

match *boxed_value {
    SomeEnum::SomeEntry(s) => drop::<String>(s), // make sure we own the string
    SomeEnum::AnotherEntry => {},
}

所以在这种情况下,一个变体存储 String。由于深度复制一个字符串的成本有点高,Rust 不会隐式地这样做。在匹配臂中,我们尝试删除 s 并断言它是 String。这意味着我们(意思是:火柴臂的主体)拥有绳子。因此,如果 match arm 拥有它但我们没有从克隆它中获得拥有的值,这意味着外部函数不再拥有它。事实上,如果您尝试在匹配后使用 boxed_value,编译器会出现移动错误。再次重申,要么你遇到编译器错误,要么没有坏事自动发生。

此外,您可以在match中写SomeEnum::SomeEntry(ref s)。在这种情况下,该字符串通过引用 s 进行绑定(因此 drop() 调用将不再起作用)。在那种情况下,我们永远不会离开 boxed_value。这就是我所说的“延迟搬家”,但我不确定这是否是一个官方术语。但这只是意味着:当模式匹配时,输入值根本不会移动,直到模式中的绑定从它移动。

最后请大家看一下this code and the generated assembly。装配是最佳的。所以再说一遍:当您来自 C++ 世界时,您可能会担心意外克隆,但这并不是您在 Rust 中真正需要担心的事情。