Search code examples
thread-safetyrustunsafe

How can I guarantee that a type that doesn't implement Sync can actually be safely shared between threads?


I have code that creates a RefCell and then wants to pass a reference to that RefCell to a single thread:

use crossbeam; // 0.7.3
use std::cell::RefCell;

fn main() {
    let val = RefCell::new(1);

    crossbeam::scope(|scope| {
        scope.spawn(|_| *val.borrow());
    })
    .unwrap();
}

In the complete code, I'm using a type that has a RefCell embedded in it (a typed_arena::Arena). I'm using crossbeam to ensure that the thread does not outlive the reference it takes.

This produces the error:

error[E0277]: `std::cell::RefCell<i32>` cannot be shared between threads safely
 --> src/main.rs:8:15
  |
8 |         scope.spawn(|_| *val.borrow());
  |               ^^^^^ `std::cell::RefCell<i32>` cannot be shared between threads safely
  |
  = help: the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<i32>`
  = note: required because of the requirements on the impl of `std::marker::Send` for `&std::cell::RefCell<i32>`
  = note: required because it appears within the type `[closure@src/main.rs:8:21: 8:38 val:&std::cell::RefCell<i32>]`

I believe I understand why this error happens: RefCell is not designed to be called concurrently from multiple threads, and since it uses internal mutability, the normal mechanism of requiring a single mutable borrow won't prevent multiple concurrent actions. This is even documented on Sync:

Types that are not Sync are those that have "interior mutability" in a non-thread-safe form, such as cell::Cell and cell::RefCell.

This is all well and good, but in this case, I know that only one thread is able to access the RefCell. How can I affirm to the compiler that I understand what I am doing and I ensure this is the case? Of course, if my reasoning that this is actually safe is incorrect, I'd be more than happy to be told why.


Solution

  • One way would be to use a wrapper with an unsafe impl Sync:

    use crossbeam; // 0.7.3
    use std::cell::RefCell;
    
    fn main() {
        struct Wrap(RefCell<i32>);
        unsafe impl Sync for Wrap {};
        let val = Wrap(RefCell::new(1));
    
        crossbeam::scope(|scope| {
            scope.spawn(|_| *val.0.borrow());
        })
        .unwrap();
    }
    

    As usual with unsafe, it is now up to you to guarantee that the inner RefCell is indeed never accessed from multiple threads simultaneously. As far as I understand, this should be enough for it not to cause a data race.