27

I want to return a value from a function which is protected by a Mutex, but cannot understand how to do it properly. This code does not work:

use std::sync::{Arc, Mutex};

fn func() -> Result<(), String> {
    let result_my = Arc::new(Mutex::new(Ok(())));
    let result_his = result_my.clone();

    let t = std::thread::spawn(move || {
        let mut result = result_his.lock().unwrap();
        *result = Err("something failed".to_string());
    });

    t.join().expect("Unable to join thread");

    let guard = result_my.lock().unwrap();
    *guard
}

fn main() {
    println!("func() -> {:?}", func());
}

Playground

The compiler complains:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:16:5
   |
16 |     *guard
   |     ^^^^^^ cannot move out of borrowed content
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
swizard
  • 2,551
  • 1
  • 18
  • 26
  • What would you like the behavior of `result_his.lock()` to be after moving the value out of `result_my`? – Shepmaster Mar 21 '15 at 18:11
  • Ideally I'd like something accepting `Mutex` by value and yielding `T`, so far I don't need that mutex anymore. But ok, I understand now this could be too complex solution. Should I accept @SBSTP 's answer now or it's better to make my own answer and accept it? – swizard Mar 21 '15 at 20:25

3 Answers3

29

In Rust 1.15, you can use Arc::try_unwrap and Mutex::into_inner:

use std::sync::{Arc, Mutex};

fn func() -> Result<(), String> {
    let result_my = Arc::new(Mutex::new(Ok(())));
    let result_thread = result_my.clone();

    let t = std::thread::spawn(move || {
        let mut result = result_thread.lock().unwrap();
        *result = Err("something failed".to_string());
    });

    t.join().expect("Unable to join threads");

    let lock = Arc::try_unwrap(result_my).expect("Lock still has multiple owners");
    lock.into_inner().expect("Mutex cannot be locked")
}

fn main() {
    println!("func() -> {:?}", func());
}

RwLock::into_inner also exists since Rust 1.6.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Rust 1.70 adds `Arc::into_inner` which guarantees that you can extract the value when you call it on every clone of the `Arc`. Not needed for the OP's usecase, but could be useful for somebody else landing on this question. – CodesInChaos May 30 '23 at 21:53
8

The best solution I found so far is to wrap the result into an Option and then take it out:

fn func() -> Result<(), String> {
    let result_my = Arc::new(Mutex::new(Some(Ok(()))));
    let result_his = result_my.clone();

    let t = std::thread::spawn(move || {
        let mut result = result_his.lock().unwrap();
        *result = Some(Err("something failed".to_string()));
    });

    t.join().expect("Unable to join thread");

    let mut guard = result_my.lock().unwrap();
    guard.take().unwrap()
}

It seems better than the mem::replace solution proposed by @SBSTP because there is no need to construct an empty T for swapping, and it prevents multiple extractions.

Community
  • 1
  • 1
swizard
  • 2,551
  • 1
  • 18
  • 26
4

You can use mem::replace to transfer ownership of a mutable reference by replacing it with a new value. (the old value is returned and moved)

use std::sync::{Arc, Mutex};
use std::mem;

fn func() -> Result<(), String> {
    let result_my = Arc::new(Mutex::new(Ok(())));
    let result_his = result_my.clone();

    let t = std::thread::spawn(move || {
        let mut result = result_his.lock().unwrap();
        *result = Err("something failed".to_string());
    });

    t.join();

    let mut guard = result_my.lock().unwrap();
    mem::replace(&mut guard, Ok(()))
}

fn main() {
    println!("func() -> {:?}", func());
}
SBSTP
  • 3,479
  • 6
  • 30
  • 41
  • 1
    Thanks for an idea! But in this case it seems that my `Option` solution is even better, because I don't have to construct useless empty `T` second time for `mem::replace` arg (it could be significant when construction is not cheap). – swizard Mar 21 '15 at 12:10
  • Well I don't know if you consider `None` to be construction, but `take` uses `mem::replace` and replaces the value by `None`. http://doc.rust-lang.org/src/core/option.rs.html#702 – SBSTP Mar 21 '15 at 15:53
  • I mean that for my example `T` is of type `Result<(), String>`, so it's no problem to provide `Ok(())` as `mem::replace` parameter, but if it was something like `fs::File` it could be nontrivial task. – swizard Mar 21 '15 at 16:01