我的数据包含在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 => {}
}
}
答案 0 :(得分:3)
第一:在Rust中,没有隐式的昂贵副本,例如在C ++中。在C ++中,默认操作是“深层复制”(通过复制构造函数或类似方法),而Rust中的默认操作正在移动。动作是一个浅表副本,(a)通常很小且便宜,并且(b)在大多数情况下可以由优化程序删除。要在Rust中获得深层克隆,您可以手动使用.clone()
。如果您不这样做,您通常不必为此担心
第二:对枚举的匹配只会查看该枚举的判别式(除非您绑定枚举字段,请参见下文)。这就是“标签”或“元数据”,它指定将枚举的哪个变体存储在值中。这个标签很小:几乎在所有情况下都适合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
。这意味着我们(意思是:匹配臂的主体)拥有该字符串。因此,当匹配臂拥有它但我们没有克隆它时,这意味着外部函数不再拥有它。实际上,如果您在比赛后尝试使用boxed_value
,则会从编译器中获取移动错误。再说一次,您会遇到编译器错误,或者没有坏事自动发生。
此外,您可以在SomeEnum::SomeEntry(ref s)
中写入match
。在这种情况下,该字符串由对s
的引用绑定(因此drop()
调用将不再起作用)。在这种情况下,我们永远不会离开boxed_value
。这就是我所说的“递延移动”,但是我不确定这是否是官方术语。但这仅表示:模式匹配时,输入值直到从模式中移出绑定为止都不会移动。
最后,请看看this code and the generated assembly。组装是最佳的。再说一遍:虽然当您来自C ++世界时可能会担心偶然的克隆,但在Rust中,这并不是您真正需要担心的事情。