Why sometimes my_arc_mutex.clone() will be freed before finishing using it?

Viewed 260

I got this error:

   --> src/client.rs:189:22
    |
189 |         let client = client.clone().lock().unwrap();
    |                      ^^^^^^^^^^^^^^                - temporary value is freed at the end of this statement
    |                      |
    |                      creates a temporary which is freed while still in use

on this function that is too big for me to post a minimum reproducible example:

async fn send_and_expect<T: OnEvent<T>>(
        client: Arc<Mutex<Self>>,
        request: rtsp_types::Message<Body>,
        unauthorized_retry: i32,
    ) -> std::result::Result<rtsp_types::Response<Body>, ClientActionError>
    where
        RtspMachine: OnEvent<T>,
        T: From<Response>,
    {
        let expected_cseq = client_parse_cseq(&request)?;
        let mut client = client.lock().unwrap();

If I simply not clone, that is, do let mut client = client.lock().unwrap();, it compiles. But I was wondering why clone() makes this error happen.

I tried to reproduce the problem to post here:

use std::sync::{Arc, Mutex};
fn main() {
    let a = Arc::new(Mutex::new(0));
    a.clone().lock().unwrap();
}

but this compiles ok.

What is wrong? Why would client.clone() sometimes be freed before finishing using it but sometimes not?

1 Answers

Unfortunately, your short example is too short, because it never binds the unwrapped MutexGuard. To reproduce the error, we need to add a binding (playground):

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

fn main() {
    let a = Arc::new(Mutex::new(0));
    let _b = a.clone().lock().unwrap();
}

rustc now yells at use righteously:

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:5:13
  |
5 |     let _b = a.clone().lock().unwrap();
  |              ^^^^^^^^^                - temporary value is freed at the end of this statement
  |              |
  |              creates a temporary which is freed while still in use
6 | }
  | - borrow might be used here, when `_b` is dropped and runs the `Drop` code for type `MutexGuard`
  |
  = note: consider using a `let` binding to create a longer lived value

So why does the borrow checker yell at us? Remember that Mutex::lock(&self) takes a reference to the Mutex. This reference stems from Arc. However, the cloned variant only lives till the end of the statement: there is no binding to a.clone(), and it passes away into oblivion at the semicolon.

The easy fix is to bind a.clone(). Usually, you would reuse the same identifier, as in Rust's Arc example (playground):

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

fn main() {
    let a = Arc::new(Mutex::new(0));
    let a = a.clone();
    let _b = a.lock().unwrap();
}

While this might seem strange in a single main function, it's much more common when moveing values into closures.

"Now hold on", you might say. "This is a shared reference, why can't I just keep using it? There's still a in the same scope!" While this could be true, it's also a trap. If the .clone().lock() line were to compile, then we could create a use-after-free error:

let a = Arc::new(Mutx::new(0));
let b = a.clone().lock().unwrap(); // reference counter increased and decreased by 1
drop(a);                           // reference counter -> 0: value gets freed                       
*b = 10;                           // whoops!
Related