如何在不复制内容的情况下对实现 Deref 的类型(例如 Box)内的值进行模式匹配? [英] How to pattern match on values inside a type implementing Deref, such as Box, without copying the contents?

查看:43
本文介绍了如何在不复制内容的情况下对实现 Deref 的类型(例如 Box)内的值进行模式匹配?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have data contained inside a Box, and would like to pattern match on it without accidentally copying the Box's contents from the heap to the stack; how do I do that?

让我们假设以下代码:

enum SomeEnum {
    SomeEntry,
    AnotherEntry,
}

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

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

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

Does this copy the enum out of the box onto the stack and pattern match on that copy, or does it do the matching directly on the value pointed to by the box?

这个变体怎么样?

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 创建对所包含值的引用.这导致了有关此语法的问题:

It seems that simply dereferencing a box does not automatically create a copy, otherwise one would not be able to create a reference to the contained value by using let x = &*boxed_value. This leads to a question about this syntax:

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().如果您不这样做,您通常不必为此担心.

First: in Rust, there are no implicit costly copies, unlike in, for example, C++. Whereas in C++, the default action is "deep copy" (via copy constructor or similar), the default action in Rust is moving. A move is a shallow copy which (a) is usually very small and cheap and (b) can be removed by the optimizer in most cases. To get deep clones in Rust you have manually use .clone(). If you don't do that, you usually don't really have to worry about this.

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

Second: matching on an enum only looks at the discriminant of that enum (unless you bind enum fields, see below). That's the "tag" or the "metadata" which specifies which variant of the enum is stored in a value. That tag is tiny: it fits in 8 bits in almost all cases (enums with more than 256 variants are rare). So you don't need to worry about that. And in your case, we have a C-like enum without any fields. So the enum only stores the tag and hence is tiny, too.

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

So what about enum fields that might be costly to copy? Like this:

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 ,你会从编译器中得到移动错误.再说一次,要么出现编译器错误,要么不会自动发生坏事.

So in this case one variant stores a String. Since deep-copying a string is somewhat costly, Rust won't do it implicitly. In the match arm we try to drop s and assert it's a String. That means we (meaning: the body of the match arm) own the string. So, if the match arm owns it but we didn't get the owned value from cloning it, that means that the outer function doesn't own it anymore. And in fact, if you try to use boxed_value after the match, you will get move errors from the compiler. So again, either you get a compiler error or no bad things automatically happen.

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

Furthermore, you can write SomeEnum::SomeEntry(ref s) in the match. In that case, the string is bound by reference to s (so the drop() call won't work anymore). In that case, we never move from boxed_value. This is something I call "deferred moving", but I'm not sure if that's an official term for it. But it just means: when pattern matching, the input value is not moved at all until a binding in the pattern moves from it.

最后,请看一下这段代码和生成的程序集.组装是最佳的.再说一次:虽然您可能会担心当您来自 C++ 世界时会出现意外克隆,但这并不是您在 Rust 中真正需要担心的.

Lastly, please take a look at this code and the generated assembly. The assembly is optimal. So once again: while you might be worried about accidental clones when you come from the C++ world, this is not really something you need to worry about in Rust.

这篇关于如何在不复制内容的情况下对实现 Deref 的类型(例如 Box)内的值进行模式匹配?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆