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 usetake
method ofOption<T>
which take the value out of theOption
and leaving aNone
in its place (this actually takes ownership of the value out). - As
bar
is 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