Move out of Arc<Mutex<T>>

Hi,
I am new to Rust.
I try to create struct which lets read data for multiple readers at the same time and here can be one writer at a time. Writer gets clone of data to change and after change original data is replaced by edited data.
Here are my code

struct RWBox
have read and write fields. read hold original data and write hold clone of this data, but only if someone wants to edit it.
After testing in playground it looks like it works, but line 37 isn't efficient, because it clones data from write field to read field. It would be faster if I could move data from write to read field, because it is not needed for write. But I could not find how to do it.

I also wonder if here is something like this already.

I haven't read your code fully, but this might work for you:

    pub fn get_read(&mut self) -> Arc<T> {
        if let Some(arc) = self.write.take() {
            match Arc::try_unwrap(arc) {
                Ok(mutex) => {
                    let inner = mutex.into_inner().unwrap();
                    self.read = Some(Arc::new(inner));
                }
                Err(arc) => {
                    self.write = Some(arc);
                }
            }
        }
        self.read.as_ref().unwrap().clone()
    }

(Playground)

Option::take moves the Arc out of the Option (leaving a None inside). Then Arc::try_unwrap moves out of the Arc (which only succeeds if the Arc has a strong_count of 1, which is checked atomically though). Finally, you can use Mutex::into_inner to consume the mutex and return the inner value. The additional .unwrap() is needed because the mutex might be poisoned by another thread panicking. The Err branch in the match is executed when the strong count wasn't 1. In that case, we store the arc back into self.write where it was taken from.

Thanks.
Than I tried to do this
if let Some(arc) = self.write {

compiler said I can't move value out of Option and said to use reference, so this was main problem. I had to use .take() on Option. Now I will know.

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

I have tried your foo, bar and baz playground. After playing with it and thinking I got some idea why error is produced. It does not have anything to do with panic. Compiler don't do in depths analysis of code.

In this example it would work, because you set "a" to None, but in threaded environment some other thread may try to get value of "a" before you have set it to None and this will result in crash.

Because compiler will never be able to check all possibilities of you code, so compiler don't let this type of operation at all.

I have read "The Book" and it was mentioned that compiler is very conservative and will not let code pass if it is possible for it to fail in some use cases.

Method .take() sets value of "a" to None at the moment of moving inner value out, so it will be impossible to fail in this. and Compiler knows take() will set value to None, but it don't know about "arg.inner = None;" line.

This is my guess.

No, this cannot happen because a mutable reference is always exclusive.

I think it's really related to stack unwinding in case of errors. But maybe someone else could explain it better?

Even if not threaded I think you just can't make mutable reference invalid. It need always be valid. Consuming variable makes it invalid.

I have made working example of you last code playground

I have used Option::take() as example. So, take() works because it don't leave variable to invalid state at all and you can change values of variable.

Yes I think you are right. Another show case:

struct NotCopy;

fn main() {
    let mut v = NotCopy;
    let r = &mut v;
    //let _t = *r; // this fails
    *r = NotCopy; // even if we replace the contents immediately
    // But this works:
    let mut v = NotCopy;
    for _ in 0..3 {
        let _t = v; // we can move out here
        v = NotCopy; // as long as we provide new contents for `v` here
    }
}

(Playground)

What I also did not consider is that when you unwind the stack, destructors may be run. So it might be a (too) huge effort for the compiler to check whether leaving an invalid state is alright.

But note how the second part of the above Playground does work. However, an interesting fact is that it won't work when you try to uncomment the if true statement below:

struct NotCopy;

fn main() {
    let mut v = NotCopy;
    for _ in 0..3 {
        let _t = v;
        //if true {
            v = NotCopy;
        //}
    }
}

(Playground)

The problem here is reference. In first playground you create
mut v,
make reference r
and try to move value of v.
I also tried to change let _t = *r; to let _t = v;
Compiler will not compile. Because here are two variables pointing to same memory (v and r), memory can't be invalidated if here are two variables pointing to it. If here are only one variable you can invalidate it.
On second part of playground you shadow v with new v, so second v don't have reference to memory it occupy. This is why here are no problem invalidating it and setting new value for it. Because you set new value for it v is valid for new loop iteration.

In second playground it does not work because compiler is unable to know if "if true" will always be true or not (we now it will be always true, but compiler is not smart enough to know it). Because of it compiler don't know if in second loop iteration v will be valid. If v is not valid you can't move non existing memory to _t.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.