I have a silly example here, just to demonstrate an issue I'm running into with another library and pattern matching.
struct Person {
name: String,
age: i32,
choice: Choices
}
#[derive(Debug)]
enum Choices {
Good,
Neutral,
Evil
}
fn find(p: Person) {
match (p.choice, p.age) {
(Choices::Good, a) if a < 80 => {
announce(p);
}
(_, a) if a >= 80 => {
println!("You're too old to care.");
}
_ => {
println!("You're not very nice!")
}
}
}
fn announce(p: Person) {
println!("Your name is {}. You are {:?}.", p.name, p.choice);
}
fn main() {
let p = Person {
name: "Bob".to_string(),
age: 20,
choice: Choices::Good
};
find(p);
}
Now the issue seems to be that during pattern matching, move semantics will kick in and take ownership over the inner struct (Thing) in my Person.
When I go to move the person on to the next method, I can't because it's been partially moved.
Compiling match v0.1.0 (file:///home/jocull/Documents/Projects/Rust/learn/match)
src/main.rs:17:13: 17:14 error: use of partially moved value: `p`
src/main.rs:17 announce(p);
^
src/main.rs:15:9: 15:17 note: `p.choice` moved here because it has type `Choices`, which is non-copyable
src/main.rs:15 match (p.choice, p.age) {
^~~~~~~~
error: aborting due to previous error
Could not compile `match`.
My gut says I need to get Rust to stop moving the value by using a reference or borrow of some kind. In this case I could change my method signature to borrow, but with some libraries you aren't always able to do that. (I am trying to deal with hyper in this case...)
Is there a way to get the match
to use references during matching instead of moving the values? Thank you!
Why?
When you make the tuple
(p.choice, p.age)
you memcpy
both p.choice
and p.age
from your Person
.
It's OK to do this for p.age
because it's a Copy
type - you can continue using the old value after memcpy
ing from it.
p.choices
is of type Choices
which is not Copy
. This means that the memcpy
is treated as a "move", so the old value is not usable. This means p
is in an invalid state, so you can't call announce
on it.
Solution #1
Since Choices
is a trivial enum
, you can just #[derive(Copy, Clone)]
. This means that you are allowed to continue using the old p.choices
.
If you can only safely make Choices
Clone
, then you'd have to clone
it in the match
instead.
Solution #2
You can take p.choices
by reference:
match (&p.choice, p.age) {
(&Choices::Good, a) if a < 80 => {
announce(p);
}
...
}
This only works because &Choices::Good
is an exact match so the borrow can be relinquished. If you had instead
match (&p.choice, p.age) {
(&x, a) if a < 80 => {
announce(p);
}
...
}
the borrow would still be active and so the move when calling announce(p)
would fail - the move would invalidate an active borrowed variable.
Notes
You're doing an awful lot of moving here - passing a few references is a lot more flexible! There's no reason for announce
to consume a Person
- it just needs to look at it for a bit. Taking by value when you could take a reference is only advisable for small Copy
types.
Note that having announce
take a reference means that the match
is allowed to also be holding on to references inside p
, which makes it more widely applicable.
to_string
is mostly for use for non-string objects. into
and to_owned
are faster and into
is also a lot shorter.
struct Person {
name: String,
age: i32,
choice: Choices
}
#[derive(Copy, Clone, Debug)]
enum Choices {
Good,
Neutral,
Evil
}
fn find(p: &Person) {
match (p.choice, p.age) {
(Choices::Good, a) if a < 80 => {
announce(p);
}
(_, a) if a >= 80 => {
println!("You're too old to care.");
}
_ => {
println!("You're not very nice!")
}
}
}
fn announce(p: &Person) {
println!("Your name is {}. You are {:?}.", p.name, p.choice);
}
fn main() {
let p = Person {
name: "Bob".into(),
age: 20,
choice: Choices::Good
};
find(&p);
}
So I try again with the new code change :)
In your current code, if you use borrowing instead of moving in the match it works.
p.age
doesn't need that only because it's a primitive type and primitive types implement the Copy
trait
But Choices do not implement the copy trait and so they are moved in the match. Which causes them to not be available when you call announce()
match (&p.choice, p.age) {
(&Choices::Good, a) if a < 80 => {
announce(p);
}
...
}
It get rids of the error about partial move. I guess it is because you moved choice in the match. But choice is a part of Person so it's partially moved.
I have not enough knowledge of Rust to truly explain why it works, so if you can add something useful, please do