3

I'm trying to put a borrowed value behind a Mutex but I'm having trouble with the borrow checker. Here's a simplified code that demonstrate the problem I encounter:

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

struct Test<'a> {
    d: PhantomData<Mutex<&'a u8>>,
}

impl<'a> Test<'a> {
    pub fn new() -> Self {
        Test { d: PhantomData }
    }
    pub fn test(&'a self) {}
}

fn main() {
    let t = Arc::new(Test::new());
    let t2 = t.clone();
    std::thread::spawn(move || {
        t2.test();
    });
}

This fails to compile with the following error

error[E0597]: `t2` does not live long enough
  --> src/main.rs:21:9
   |
19 |     let t2 = t.clone();
   |         -- lifetime `'1` appears in the type of `t2`
20 |     std::thread::spawn(move || {
21 |         t2.test();
   |         ^^-------
   |         |
   |         borrowed value does not live long enough
   |         argument requires that `t2` is borrowed for `'1`
22 |     });
   |     - `t2` dropped here while still borrowed

I guess the compiler thinks t2 might be borrowed to somewhere else when calling test(). It seems if I modify the type of the d field in struct Test to anything excluding Mutex, such as d: Option<&'a u8>, it works fine. What is so special about Mutex since it's just a wrapper around an UnsafeCell?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
hadroncfy
  • 33
  • 1
  • 3
  • 1
    TL;DR the duplicates — wrapping a reference in a mutex doesn't change the fact that the referred-to-value may stop existing before the reference is used. You probably want to use _scoped threads_. – Shepmaster Jan 05 '21 at 16:27
  • Thanks for your help! But i still don't understand why there's no error if I _don't_ use mutex and instead use a reference directly or wrap it with somthing other than mutex, in this case the referenced value could also be dropped before used. – hadroncfy Jan 05 '21 at 17:00
  • In that case [`'a` is inferred as `'static`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9c0f238baabef60185937c4ea819c900) which is fine to pass between threads. – Shepmaster Jan 05 '21 at 17:07
  • Oh i see. But mutex still cause the error even if I [explicitly take 'a to be 'static](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e9377febaa742efc03206d41c1a6b022) – hadroncfy Jan 06 '21 at 14:00
  • You also have `fn test(&'a self) {}` which is nonsense the vast majority of the time. That means that the instance that the method is called on has to live as long as `'a`. When you set it to `'static`, your instance doesn't live that long. – Shepmaster Jan 06 '21 at 14:05
  • But my instance is behind an Arc so it should live as `'static`? There's also no error if I [remove the mutex](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=abb518adadacc71c5cd6ece0395b7d3f) – hadroncfy Jan 06 '21 at 14:47
  • No, the `Arc` lives until it is dropped, it is not a `static` value. – Shepmaster Jan 06 '21 at 14:52

1 Answers1

4

What is so special about Mutex since it's just a wrapper around an UnsafeCell?

The difference is variance.

&'a T is covariant in the lifetime 'a: You can coerce an immutable reference with a longer lifetime to one with a strictly shorter lifetime, because it is always safe to pass &'long T where &'short T is expected. This is why the code compiles without the UnsafeCell.

But UnsafeCell<&'a T> is invariant in 'a because it has interior mutability: If you could pass UnsafeCell<&'long T> to code that takes UnsafeCell<&'short T>, that code could write a short-lived reference into your long-lived cell. So it is not safe to coerce an UnsafeCell to have a different lifetime.

(The same is true for any type that lets you mutate the reference it contains, e.g. Mutex<&'a T> or &mut &'a T.)

mbrubeck
  • 869
  • 6
  • 5