如何在不复制内容的情况下对实现 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 中真正需要担心的事情。
我的数据包含在 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 中真正需要担心的事情。