UnsafePinned + pin projections: A step toward more ergonomic shared mutable state?

Lately, I've been exploring new RFC's and updates on the Rust roadmap. A couple proposals have caught my attention: UnsafePinned (3467) and field projections (3735).

https://rust-lang.github.io/rfcs/3467-unsafe-pinned.html

I've been struggling with some code recently and I confess I have frequently wished for the closest equivalent to a shared mutable reference. The same rules as C++, with relatively few restrictions, that doesn't require filling my code with ".borrow_mut()" or ".get()/.set(...)". Since my code is single-threaded, access conflicts aren't a concern, but the friction involved remains a significant pain point.

I'm aware these RFC's weren't specifically designed to solve my problem, but for programmers more experienced with Rust than I am, do you think this will make shared mutable state more ergonomic? Is there another approach I should be considering?

This is not true in the general case — the mutable state must be protected against reentrant access as well as multi-threaded access. Think recursion, but not just explicit recursion: also implicit recursion through (ab)use of callbacks, including global ones like the panic hook.

Of course, in a particular program it may be the case that no such conflicts arise, but you cannot use such global reasoning to say “this library is sound”, so its applicability is limited.

I personally am very much looking forward to being able to use these things to construct self-referential structs that are on an unambiguously sound foundation and don’t have unnecessary heap allocations. This will be useful to make some patterns involving shared mutable state, like “owned mutex guards”, expressible without as much unsafe code. But I don't see how they’re applicable to the kind of thing you want.

3 Likes

This is a misconception, it still is. The Problem With Single-threaded Shared Mutability - In Pursuit of Laziness

Since you mentioned Cell/RefCell's methods, those types don't even work with multithreading, they only exist to handle single threaded shared mutability.

Not really, this is mostly just a building block for self-referential structs containing generic types, not for shared mutability.

Generally speaking, try to avoid shared mutability in the first place.

1 Like

The general approach to avoid shared mutable state is to seperate determining the update from performing the update, either in time or space (memory). This both avoids the immediate borrow checker and logical issue of using data that might be being updated, but it also (hopefully!) means you have a well defined logical update that can be atomically applied so you don't have conflicting updates from multiple sources.

This can be as simple as adding the index to remove in a loop before removing in a loop, or it can be as encompassing as always producing an entire new global state each update step. This is essentially what immutable libraries are too, though they can be much less efficient than directly performing it, for example with a "double buffering" approach where you're reusing the same allocations.

Equivalently an actor style pattern where mutable state is only accessable via channels that send commands and updates to each other, so all updates are buffered and processed sequentially. It trades simplicity of global design for complexity of local reasoning about why an update happens, but it's generally a pretty good trade.

There's a few other approaches that work well in specific cases, but approaches that work globally tend to be this in some sense.

2 Likes