Rust - `Option` take
Problematic Code
struct Foo {
bar: Bar,
}
impl Foo {
fn foo(&self) {
self.bar.bar();
}
}
struct Bar {}
impl Bar {
fn bar(self) {}
}
fn main() {
let foo = Foo { bar: Bar {} };
foo.foo();
}
Output
💤 via 𝗥 v1.42.0 cargo build
Compiling foo v0.1.0 (/tmp/foo)
error[E0507]: cannot move out of `self.bar` which is behind a shared reference
--> src/main.rs:7:9
|
7 | self.bar.bar();
| ^^^^^^^^ move occurs because `self.bar` has type `Bar`, which does not implement the `Copy` trait
error: aborting due to previous error
For more information about this error, try `rustc --explain E0507`.
error: could not compile `foo`.
To learn more, run the command again with --verbose.
Fix by Wrapping With Option<T>
The problem here is because Bar.bar take ownership of self (rather than reference as &self). In this case, as we are holding the reference of bar (i.e. foo.bar) and because Bar doesn’t implement Copy trait, so we can not transfer the ownership out.
One fix is as below:
struct Foo {
bar: Option<Bar>,
}
impl Foo {
fn foo(&mut self) {
if let Some(b) = self.bar.take() {
b.bar();
}
}
}
struct Bar {}
impl Bar {
fn bar(self) {}
}
fn main() {
let mut foo = Foo { bar: Some(Bar {}) };
foo.foo();
}
There are following changes:
- Change type of
Foo.bar:Bar->Option<Bar>and change the impl ofFoo.foo, where we usetakemethod ofOption<T>which take the value out of theOptionand leaving aNonein its place (this actually takes ownership of the value out). - As
baris a field inFoo, we need also changeFoo.foo()’s signature fromfoo(&self)tofoo(&mut self).
But one thing to point out is that as we are using take(), which will take the value out of Option and leave it with None, after we calling foo.foo(), then foo.bar is a None, if we call foo.foo() again, it will not match the if let pattern again.
Other Solutions
By referring to rustc --explain E0507, there are more possible ways to fix, e.g. by implementing the Copy trait:
//...
#[derive(Clone, Copy)]
struct Bar {}
//...
Comments