Passing mutable references

Seem to be going round in circles again. I pass lots of refs around using Arc but these are not my objects and don't need to be mutable. I have one user type instance that I can't really avoid passing around and it has to be mutable but which ever way I try to do this I get stuck.
As I understand it I should be able to use Rc/RefCell to do this. There is a lot of code so I will post minimal but its on GitHub if the full files are useful.

This is the field in the local struct.

pub i_cc_out : Rc<RefCell<protocol::cc_out::CCDataMutex>>,

and the initialisation.

let i_cc_out = Rc::new(RefCell::new(protocol::cc_out::CCDataMutex::new()));

At the moment I am only trying to pass this to one module which is a thread. It will be passed to at least one other module (actually the GUI on the main thread).
The thread is started from the local module

// Start the UDP writer thread
                opt_writer_join_handle = Some(
                    udp::udp_writer::writer_start(w_r.clone(), 
                    arc3, arc5, 
                    rb_audio.clone(), audio_cond.clone(), &mut i_cc_out.borrow_mut()));

No compile errors showing at this point.
In the writer thread module

//==================================================================================
// Thread startup
pub fn writer_start(
        receiver : crossbeam_channel::Receiver<messages::WriterMsg>, 
        p_sock : Arc<socket2::Socket>,
        p_addr : Arc<socket2::SockAddr>, 
        rb_audio : Arc<ringb::SyncByteRingBuf>, 
        audio_cond : Arc<(Mutex<bool>, Condvar)>,
        i_cc_out : &mut RefMut<protocol::cc_out::CCDataMutex>) -> thread::JoinHandle<()> {
    let join_handle = thread::spawn(  move || {
        writer_run(receiver, &p_sock, &p_addr, &rb_audio, &audio_cond, &mut i_cc_out);
    });
    return join_handle; //join_handle;
}

fn writer_run(
    receiver : crossbeam_channel::Receiver<messages::WriterMsg>, 
    p_sock : &socket2::Socket,
    p_addr : &socket2::SockAddr, 
    rb_audio : &ringb::SyncByteRingBuf,
    audio_cond : &(Mutex<bool>, Condvar),
    i_cc_out : &mut protocol::cc_out::CCDataMutex) {
    println!("UDP Writer running");

    // Instantiate the runtime object
    let mut i_writer = UDPWData::new(receiver, p_sock, p_addr, rb_audio, audio_cond, i_cc_out);

    // Exits when the reader loop exits
    i_writer.writer_run();

    println!("UDP Writer exiting");
    thread::sleep(Duration::from_millis(1000));
}

The error occurs when trying to spawn the thread.
error[E0277]: NonNull<CCDataMutex> cannot be sent between threads safely
--> src\app\udp\udp_writer.rs:175:23
|
175 | let join_handle = thread::spawn( move || {
| ^^^^^^^^^^^^^ NonNull<CCDataMutex> cannot be sent between threads safely
|
= help: within RefMut<'_, CCDataMutex>, the trait Send is not implemented for NonNull<CCDataMutex>
= note: required because it appears within the type RefMut<'_, CCDataMutex>
= note: required because of the requirements on the impl of Send for &mut RefMut<'_, CCDataMutex>
note: required because it's used within this closure
--> src\app\udp\udp_writer.rs:175:39
|
175 | let join_handle = thread::spawn( move || {
| ^^^^^^^
note: required by a bound in spawn
--> C:\Users\User.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\std\src\thread\mod.rs:653:8
|
653 | F: Send + 'static,
| ^^^^ required by this bound in spawn

error[E0277]: std::cell::Cell<isize> cannot be shared between threads safely
--> src\app\udp\udp_writer.rs:175:23
|
175 | let join_handle = thread::spawn( move || {
| ^^^^^^^^^^^^^ std::cell::Cell<isize> cannot be shared between threads safely
|
= help: the trait Sync is not implemented for std::cell::Cell<isize>
= note: required because of the requirements on the impl of Send for &std::cell::Cell<isize>
= note: required because it appears within the type cell::BorrowRefMut<'_>
= note: required because it appears within the type RefMut<'_, CCDataMutex>
= note: required because of the requirements on the impl of Send for &mut RefMut<'_, CCDataMutex>
note: required because it's used within this closure
--> src\app\udp\udp_writer.rs:175:39
|
175 | let join_handle = thread::spawn( move || {
| ^^^^^^^
note: required by a bound in spawn
--> C:\Users\User.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\std\src\thread\mod.rs:653:8
|
653 | F: Send + 'static,
| ^^^^ required by this bound in spawn

If you're doing multi-threading, then you shouldn't be putting your types inside Rc/RefCell. They are single-threaded, and this is the source of your error.

That said, there's also another problem: i_cc_out is a reference, and you cannot move references across a thread::spawn boundary.

1 Like

Are you saying there is no way to share a mutable reference across threads even when its protected by a Mutex then.

There's something called a scoped thread which allows sharing of references across threads, but it doesn't quite work in the same way and would require other changes for you to be able to use it.

You also talk about mutexes, but they're not relevant. Mutable references are exclusive, so you don't need a mutex if you have mutable access.

Usually you would not use a reference to move things across threads. If you don't need shared access, moving the object itself instead of just a reference would do it. If you need shared access, you would use an Arc instead of a reference.

I don't think an Arc works in this instance and Rc/RefCell as you point out wont work either. I think this area is so fraught with problems that if you can't make the application a pure tree structure it's going to be painful to implement, particularly if its threaded. I've backtracked to what I had working and will have to think how to do this without having a shared mutable reference.and without completely destroying my module structure. If anyone knows a simple elegant way to achieve shared mutables across threads please tell.

They can be relevant. If you are stuck with a mutable reference for whatever reason, you can use a Mutex (or RwLock) to coordinate one-at-a-time access to it from multiple scoped threads, in the exact same way as a Mutex would do for mutating a fully owned value.

That's not necessarily the best option in many cases but it is an option.

The multi-threaded equivalent of what you started with (Rc<RefCell<T>>) is what you obtain by simply replacing the shared-ownership and the shared-mutability primitives with their respective thread-safe equivalents: Arc<Mutex<T>>. You can use a Mutex wrapped in an Arc to ensure that an object lives as long as you have a reference to it, and that you can access it from multiple threads in a synchronized manner.

What you might be struggling with is that you perhaps want to store simple references at the same time. That usually won't work; you can only get an immutable &T from an Arc<T>, and only one that lives as long as the Arc instance itself (because it may be the last one that holds on to the data). So if you try to use such a short-lived immutable reference as a long-lived or mutable one, trouble ensues.

The whole point of wrapping things in an Arc and a Mutex is to achieve shared ownership and shared mutability through these types; they don't – can't – magically grant run-time lifetime and synchronization capabilities to the purely compile-time nature of simple references. So the correct way to build up a state/data structure via Arc<Mutex<T>> is usually to go all in, and not to try smuggling references out of them longer than it is strictly necessary.

1 Like

Thanks for the explanation. I have to admit I just don't understand this yet. There seems to be conflicting information as I've read many times that refs inside an Arc are by definition not mutable but you are clearly saying they are.
The thread that I want to create the user defined type is the main thread and I will need to pass that to another module which is also on the main thread. I also need to pass it to a user thread which is long running, i.e. until the program terminates. I don't necessarily need to store it in the module that creates it but it will need to be stored in the two modules that I pass it to, one main thread and one a user thread. Is this possible using Arc<Mutex> or not. If you say it's possible I will knock up a demo which I will no doubt fail to get working as a starting point.

Rust's &T vs &mut T types being "immutable" and "mutable" references/borrows are misnomers. They should have instead been called "shared" and "exclusive". An exclusive borrow implies the ability to mutate, but it is not the only way to mutate.

It is true you can only get a &T out of an Arc<T> [1], which means you can only have a shared reference. If you need to mutate the data behind the reference/Arc, then, you need some form of shared mutability -- an ability to mutate data behind a shared reference.

In Rust we call this "interior mutability". Interior mutability, roughly speaking, loosens Rust's aliasing guarantees and replaces compile-time memory safety checks with run-time checks, e.g. synchronization primitives.

RefCell and Mutex are both shared mutability [2] "smart pointers" that let you go from &T to &mut T.


  1. ...or a Rc<T>! ↩ī¸Ž

  2. (interior mutability) ↩ī¸Ž

2 Likes

Indeed, you can only get a &T from an Arc<T> and not a &mut T. However, Mutex provides interior mutability. Just like you can borrow_mut() a RefCell, you can similarly lock() a Mutex. Both are interior mutability types, which use runtime checking to safely hand out non-aliasing &mut T even if you have a &RefCell<T> or a &Mutex<T>. Hence, all in all, you can get a &Mutex<T> from an Arc<Mutex<T>>, which in turn allows you to get a &mut T.

It is – or at least, based on your description only, I don't see any fundamental reason why it should be impossible.

So, taking that a bit further. The type I am passing is wrapped in a Mutex so I can easily do Arc<Mutex> with it. The methods of that type all lock the Mutex and are all short calls just updating state. My question is is that going to work or do I have to lock when I spawn the thread which would only work for short lived threads.

Using an Arc like that should work. There's no issue with long-lived threads.

I don't know how to make this mutable. I just added a test

let mut i_cc = Arc::new(protocol::cc_out::CCDataMutex::new());

Not storing it anywhere just call the writer thread.

opt_writer_join_handle = Some(
                    udp::udp_writer::writer_start(w_r.clone(), 
                    arc3, arc5, 
                    rb_audio.clone(), audio_cond.clone(), i_cc.clone()));

Then in the writer thread

//==================================================================================
// Thread startup
pub fn writer_start(
        receiver : crossbeam_channel::Receiver<messages::WriterMsg>, 
        p_sock : Arc<socket2::Socket>,
        p_addr : Arc<socket2::SockAddr>, 
        rb_audio : Arc<ringb::SyncByteRingBuf>, 
        audio_cond : Arc<(Mutex<bool>, Condvar)>,
        i_cc : Arc<protocol::cc_out::CCDataMutex>) -> thread::JoinHandle<()> {
    let join_handle = thread::spawn(  move || {
        writer_run(receiver, &p_sock, &p_addr, &rb_audio, &audio_cond, &i_cc);
    });
    return join_handle; //join_handle;
}

fn writer_run(
    receiver : crossbeam_channel::Receiver<messages::WriterMsg>, 
    p_sock : &socket2::Socket,
    p_addr : &socket2::SockAddr, 
    rb_audio : &ringb::SyncByteRingBuf,
    audio_cond : &(Mutex<bool>, Condvar),
    i_cc : &protocol::cc_out::CCDataMutex) {
    println!("UDP Writer running");

    // Instantiate the runtime object
    let mut i_writer = UDPWData::new(receiver, p_sock, p_addr, rb_audio, audio_cond, i_cc);

    // Exits when the reader loop exits
    i_writer.writer_run();

    println!("UDP Writer exiting");
    thread::sleep(Duration::from_millis(1000));
}

Then I store it in the writer thread but can't make it mutable at this point. What do I need to add? I've tried adding &mut but it wont have it anywhere.

When you have a shared reference to a mutex, you can get mutable access to its contents by calling its lock function. The lock function itself can be called with just a shared reference, so the function signature you posted looks good.

But that was my query. I don't want to lock it until I call the methods which all take the lock. If I lock at this point nothing else can get access until the thread exits.

You can have a &Mutex without having locked it. You can just wait with calling lock until you want to.

1 Like

I think I had the wrong end of the stick. I wrapped it in another Mutex which I can lock before I pass the reference downstream for encoding or call a method locally. The Mutex I have in the module I think is pointless and can be removed as I can only access it within the module anyway. It's working now so I can try passing it to the GUI module. Thanks for the patience hopefully another pattern is burnt into my brain.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.