Yeah, I just verified. I get the same error when I try to move out of self.write directly, even if I set a new value immediately:
pub fn get_read(&mut self) -> Arc<T> {
let arc_option = self.write;
self.write = None;
if let Some(arc) = arc_option {
(Playground)
Error:
error[E0507]: cannot move out of `self.write` which is behind a mutable reference
--> src/main.rs:35:26
|
35 | let arc_option = self.write;
| ^^^^^^^^^^
| |
| move occurs because `self.write` has type `Option<Arc<Mutex<T>>>`, which does not implement the `Copy` trait
| help: consider borrowing here: `&self.write`
For more information about this error, try `rustc --explain E0507`.
I think this is because self
will be left in an invalid state if the program would panic, for example, and the compiler isn't smart enough to notice this can't ever happen (I think).
I experimented a bit more:
fn foo() {
let mut option = Some("Foo".to_string());
for _ in 0..2 {
if let Some(s) = option {
// Note: `s` is an owned `String` now
println!("{s}");
option = None; // alternatively: option = Some(s);
}
}
}
fn bar() {
struct A { inner: Option<String> }
let mut a = A { inner: Some("Bar".to_string()) };
for _ in 0..2 {
if let Some(s) = a.inner {
println!("{s}");
a.inner = None;
}
}
}
fn baz() {
struct A { inner: Option<String> }
let mut a = A { inner: Some("Baz".to_string()) };
fn func(arg: &mut A) {
for _ in 0..2 {
// We need to write arg.inner.take() in the following line, but
// why didn't we need to do that in `foo` and `bar`?
// if let Some(s) = arg.inner {
if let Some(s) = arg.inner.take() {
println!("{s}");
arg.inner = None;
}
}
}
func(&mut a);
}
fn main() {
foo();
bar();
baz();
}
(Playground)
Output:
Foo
Bar
Baz
We need to use take
only in the last function ("baz
"). I think that is because the compiler can ensure in case of the first two functions ("foo
" and "bar
") that if a panic happens, the variables option
and a
, respectively, will cease to exist when a panic occurs, so it's okay if they are in an invalid state.
Do I understand this right?
P.S.: I think this is a common obstacle when implementing std::ops::Drop::drop
, because it operates on &mut Self
and even if you know that *self
will be destroyed after the drop handler finishes, you can't move out of fields:
fn consumes_string(s: String) {
println!("Got: {s}")
}
struct Foo {
inner: String,
}
impl Drop for Foo {
fn drop(&mut self) {
consumes_string(self.inner);
// But we can do the following, which leaves an empty `String` in `self.inner`:
//consumes_string(std::mem::take(&mut self.inner));
}
}
fn main() {
let foo = Foo { inner: "Hello".to_string() };
drop(foo);
}
(Playground)
Errors:
Compiling playground v0.0.1 (/playground)
error[E0507]: cannot move out of `self.inner` which is behind a mutable reference
--> src/main.rs:11:25
|
11 | consumes_string(self.inner);
| ^^^^^^^^^^ move occurs because `self.inner` has type `String`, which does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` due to previous error