Rc is !Send. Rc objects cannot be shared between threads due to race conditions on dropping. However, if all clones of an Rc object were to be sent to another thread as one, it seems to me that there would be no issue with safety.
Is there any way of using Rc<RefCell> inside a spawned tokio task without pinning it to a single thread? Arc is out of the question due to it requiring the contained type to by Sync for it to be Send.
If the whole task is sent to another thread, then all clones of an Rc by definition would also be sent to that thread. !Send seems too restrictive.
I expect it would add too much runtime overhead to try creating wrapper crate, with safe code for usage. Likely could be worse than Arc<Mutex/RwLock>.
If performance is needed (while still having dynamic allocation) using unsafe blocks are needed, then you probably question the need for Rc too. Plus lots of testing and rigour. More than likely it is just not worth it.
The type system cannot understand the soundness condition "none of these Rcs will exit the bounds of the task". So, you might choose to use a carefully chosen unsafe impl Send to assure the compiler of the property it cannot check.
That said, it really isn't a big deal to use an "overkill" Arc<Mutex> over Rc<RefCell>. Try that, and only reach for unsafe after determining that the performance is inadequate.
But also consider if you can write your single task in a way that doesn't need Rc<RefCell>. There are often other ways.
This reminds me about this discussion on SO. In short, this might be possible with some other marker trait which marks items that are "sound to send, but unsound to capture", that is, that can be sent if and only if they are created from scratch in the async block. But there are a lot of complications hindering this approach, mainly the thread-locals.
Sending all instances together to another thread would be safe, but Rust's type system cannot reason about such case (AFAIK Swift can, or at least is planning to). Send gives broader guarantees than what's needed for Rc, and couldn't prevent cases where you have Rc<ActuallyNotSafeToSend> types (that may depend on thread-local storage, thread's ID, etc.)
Seems like there should be a trait for things that are okay to send, as long as all owning objects up to the containing task can be sent safely. This should be the case for Rc and Rc<RefCell>. Have there been any attempts at implementing this?
It can't be implemented with just a marker trait, because the problem doesn't fit Rust's type system.
Currently, all non-borrowed instances are completely independent. There's nothing in the type system to track owning instances and tie them together. Rust doesn't actually know that Rc is shared, or that the instances are related. It's implemented as a library type, and to the language they're all completely independent objects, like Box.
The only thing Rust has for tracking relationships between objects is borrowing and lifetimes. But lifetimes don't support self-referential relationships, have a static scope, and make the lender immovable. They prevent things from moving, so using them to move things together would be a completely new thing they can't do.
Still, solving self-referential types problem would most likely be the closest to a possible solution. This would let you avoid Rc entirely, and use references instead, and then move the object they're borrowing together with its loans.
I wonder if you could make something like this work via a Send+!Sync witness type that is required to actually access the data, so that only one thread at a time can be messing with the shared constellation. (E.g. something along the lines of GhostCell or TCell)