问题
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?
Let's assume the following code:
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?
What about this variant?
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 => {}
}
}
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 => {}
}
}
回答1:
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
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 bit 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 => {},
}
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 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 when the match arm owns it, but we didn't clone it, that means that the outer function can'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.
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 a official term for that. But it just means: when pattern matching, the input value is not moved at all until a binding in the pattern moves from it.
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.
来源:https://stackoverflow.com/questions/53132035/how-to-pattern-match-on-values-inside-a-type-implementing-deref-such-as-box-wi