Is There an Interior Mutability Primitive For What I'm Thinking?

Hey there!

I've got a use-case where I need some sort of interior mutability, similar to a mutex, but that doesn't need to be locked. I have a struct that I need to store that I can only have a read-only reference to, and I need to be able to take a reference to the internal value without having to lock it and obtain a mutex guard. It needs to be able to dereference to the inner value.

But I also need to be able to update that value. In this case, I only need to be able to update the value one time over the life of the application, and I'm fine if that update doesn't happen immediately if that value is currently being read from.

Essentially I need Tokio's watch channel, but I don't want to pull in tokio and I don't need anything async. I would like it to be as small as possible, or even, if at all possible, something simple enough to implement myself either without unsafe or without any difficult to prove soundness questions.


Edit: I just realized this might be impossible without some sort of lock/borrow like Mutex and Tokio's watch do. I just don't want to have to lock because I only need this behavior on one platform, and I wanted to be able to slip this wrapper around my existing type without the rest of the app having to use it any differently ( i.e. it just dereferences to the latest stored value without having a separate RAII guard )

I may just have to use a mutex.

If you want to update the value, even just a single time, you need some sort of guard to prevent a race condition. You can't just take a direct reference to the inner value. Tokio's watch::channel also works like this: Receiver::borrow() returns a Ref, which is just a wrapper around an RwLockReadGuard.

It looks like what you mean by this is that you don't need it to be thread-safe. In this case, you should probably use RefCell, and call try_borrow() / try_borrow_mut() in order to be explicitly notified whether the value can be updated/read, instead of panicking.

1 Like

There's a synchronous watch channel in the watch crate. Besides that, check out arc-swap.

1 Like

This also might be a case for RWLock. My understanding is that should be faster than a Mutex for Readers, but comes with the gotcha that Writers may stall forever if Readers overlap.

I was thinking more like a channel, where the sender doesn't have to have exclusively lock the receiver to send values.

Still, I'm technically only using this on WASM, which doesn't really have threads and therefore might not need to be thread safe. It's just that Rust doesn't know WASM doesn't have threads, and if one day ( or with certain rustc flags enabled ) WASM does have threads, then I'd kind of rather design with the possibility in mind.

Oh, those both look cool, thanks.

So far the only crate I've seen to do what I'm thinking without a guard is the tripple_buffer::Output::read, which somehow manages to return a normal reference instead of a guard. I'm not for sure about how much I trust that crate yet, or the MPL license, though.

I started thinking it was impossible, but that does it. Granted, maybe it's not sound. :man_shrugging:


I think for now I'll just end up creating a wrapper type that provides a no-op lock function on platforms that don't need the value to change, and then uses an actual mutex on platforms where the value does need to change.

I just didn't want to have to add the annoying lock call to all my code, when only one platform actually needs the mutex.

If you are on WASM, you should be able to just use Rc<RefCell<...>>.

2 Likes

That's not quite true. The need for a guard doesn't arise from threading. It arises from shared mutability, which means that you have to move borrow checking from compile time to runtime. It just so happens that this is also sufficient for ensuring thread safety.

1 Like

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.