Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.3k views
in Technique[技术] by (71.8m points)

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

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 => {}
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 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 => {},
}

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.

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.

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...