1

I need to unsafely define a Rust struct that I can share between 2 threads and mutate the content of the struct from both threads.

I do not want to use Mutex nor RwLock because I want to implement the thread safety myself. For performance concerns, I do not want to check the mutex when time I want to access the content and I know it is not in the critical section.

If I only use Arc to share the struct between threads, I get cannot borrow data in an Arc as mutable and help: trait DerefMut is required to modify through a dereference, but it is not implemented for std::sync::Arc<Foo>.

The safe way to do this:

struct Foo {
    bar: usize,
}

impl Foo {
    pub fn set_bar(&mut self, a: usize) {
        self.bar = a;
    }
}

fn main() {
    let mut foo = Foo { bar: 32 };
    foo.bar = 33;

    let foo_arc = std::sync::Arc::new(std::sync::Mutex::new(foo));
    let foo_arc_2 = std::sync::Arc::clone(&foo_arc);

    let handle = std::thread::spawn(move || {
        foo_arc_2.lock().unwrap().set_bar(32);
    });
    foo_arc.lock().unwrap().set_bar(31);
    handle.join().unwrap();
}

What I unsafely want to achieve:

struct Foo {
    bar: usize,

    // My own lock
    // lock: std::sync::Mutex<usize>,
}

unsafe impl Sync for Foo {}

impl Foo {
    pub fn set_bar(&mut self, a: usize) {
        self.bar = a;
    }
}

fn main() {
    let mut foo = Foo { bar: 32 };
    foo.bar = 33;

    let foo_arc = std::sync::Arc::new(foo);
    let foo_arc_2 = std::sync::Arc::clone(&foo_arc);

    let handle = std::thread::spawn(move || {
        foo_arc_2.set_bar(32);
    });
    foo_arc.set_bar(31);
    handle.join().unwrap();
}

I might not have to use Arc and use something more low level unknown to me at the moment.

1 Answer 1

3

If you want to do this to later use it in production, don't do it! Many people smarter than you and me already done this correctly. Use what they wrote instead. If you want to do this as an exercise, or for learning purposes, then go ahead and do it.

If you want to provide a type with interior mutability then you must use UnsafeCell. This type is at a core of every interior mutability in rust and using it is the only way to get a &mut T from &T. You should read really carefully it's documentation, the documentation of the cell module and The Nomicon (preferably all of it, but at least concurrency chapter).

If you prefer watching videos, Jon Gjengset has, among many others, this amazing video on cell types. And this video on atomic memory and implementing (bad) mutex.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you very much, great answer! If I wanted to use this for more than an exercise, there is no way to avoid using the Mutex, right @Aleksander Krauze ?
@javier Well, it depends. std::sync::Mutex is a good place to start, but it is not the best solution in high contingency scenarios. There is no universal answer, because each problem requires different approach and each person might be willing to take different trade-offs. You can look at lib.rs and see if you see there something that looks nice for you.
Nitpick: "get a &mut T from &T" is UB even with UnsafeCell. What it allows is going from &Wrapper<T> to &mut T (where Wrapper can be Cell, RefCell, Mutex, etc., including UnsafeCell itself).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.