Reputation: 7403
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?
Upvotes: 2
Views: 1475
Reputation: 8224
(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
Upvotes: 4
Reputation: 3970
*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 (_
).
Upvotes: 5