2
votes

So I'm looking at the impl for is_some() for Option and I noticed that it uses match *self {} under the hood...so it moves it internally.

My question is, if it gets moved, how am I able to do something like this? https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f094da12290b77bad526674467e51043

fn main() {
    let x = Option::Some(3);
    x.is_some();
    x.is_some();
}

My expectation is that I should be able to call is_some() only once, and the next time I call it I should get some sort of error saying it's been moved...but no, it all compiles fine.

What am I misunderstanding?

2
Does is_some take self by value?Romário
Another thing to consider is that there is a impl<T> Copy for Option<T> where T: Copy and the integer primitives are all Copy. Therefore, Option<{integer}> is also Copy.Wesley Wiser

2 Answers

5
votes

*self in match *self { ... } does not move (or copy) what self points to. From "The Rust Reference" (emphasis mine),

A match behaves differently depending on whether or not the scrutinee expression is a place expression or value expression. If the scrutinee expression is a value expression, it is first evaluated into a temporary location, ...

When the scrutinee expression is a place expression, the match does not allocate a temporary location; however, a by-value binding may copy or move from the memory location. ...

*self is a place expression. From "The Rust Reference" (emphasis mine),

Expressions are divided into two main categories: place expressions and value expressions. ...

A place expression is an expression that represents a memory location. These expressions are paths which refer to local variables, static variables, dereferences (*expr), array indexing expressions (expr[expr]), field references (expr.f) and parenthesized place expressions. All other expressions are value expressions.

A value expression is an expression that represents an actual value.


You might also be interested to know that the Some(_) => true arm in the match body does not bind anything. From "The Rust Reference",

Unlike identifier patterns, it does not copy, move or borrow the value it matches.

where "it" means the wildcard pattern (_).

3
votes

(See the @dkim's answer for a more official and cited answer)

If the signature of the function accepts by reference, it does not take ownership of the value. Option.is_some() actually takes &self not self.

The interesting part is how *self is allowed to be used in a function that receives &self when Self: Copy is not bounded.

To test this, let's create a minimal example containing something similar: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d7b137b74b5cd8f8bb57398ae01bf4e3

#[derive(Debug)]
pub enum A {
    X(String),
    Y(i32),
}
pub fn f(a: &A) {
    match *a {
        A::X(_) => {
            //  dbg!(s);
        }
        A::Y(_i) => {
            //  dbg!(i);
        }
    };
}

This compiles fine. But let's change the A::X(_) pattern to A::X(_s): https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=93030e5c3f84532532c5db966c798bd6

#[derive(Debug)]
pub enum A {
    X(String),
    Y(i32),
}
pub fn f(a: &A) {
    match *a {
        A::X(_s) => {
            //  dbg!(s);
        }
        A::Y(_i) => {
            //  dbg!(i);
        }
    };
}

This fails to compile:

error[E0507]: cannot move out of `a.0` which is behind a shared reference
 --> src/lib.rs:7:11
  |
7 |     match *a {
  |           ^^ help: consider borrowing here: `&*a`
8 |         A::X(_s) => {
  |              --
  |              |
  |              data moved here
  |              move occurs because `_s` has type `std::string::String`, which does not implement the `Copy` trait

So it appears that dereferencing a non-Copy enum is perfectly fine, as long as it wouldn't be used for moving inner non-Copy values. _ is fine because it guarantees that the underlying value would never be used, while _s does not compile because it is just a normal allow(unused) variable.

This also makes sense because it allows the same match arm to work on both Copy and non-Copy types, as long as no usages violate the ownership rules