| Crates.io | thread_cell |
| lib.rs | thread_cell |
| version | 0.2.3 |
| created_at | 2025-09-27 09:27:54.508651+00 |
| updated_at | 2025-10-16 18:18:17.564484+00 |
| description | Safe, Send + Sync access to !Send/!Sync data by isolating it on a dedicated thread and interacting with it through message passing. Perfect for Rc, RefCell, and other single-threaded types. |
| homepage | |
| repository | https://github.com/mcmah309/thread_cell |
| max_upload_size | |
| id | 1857125 |
| size | 74,974 |
thread_cell is a Rust crate that gives you safe, Send/Sync access to non-Send/Sync data by isolating it on a dedicated thread and interacting with it through message passing.
Unlike Arc<Mutex<T>>, ThreadCell<T> does not require T: Send + Sync, yet you can still share it across threads. This makes it perfect for types like Rc, RefCell, or FFI handles that are not Send.
Sometimes you have data that must stay on a single thread (e.g. Rc, RefCell, or certain graphics/audio/DB handles).
But you still want to send it around safely — possibly to other threads — and run operations on it in a serialized manner.
Rc safely shareable across threadsuse std::rc::Rc;
use std::cell::RefCell;
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
struct NonSendSync {
parent: Option<Rc<RefCell<NonSendSync>>>,
children: Vec<Rc<RefCell<NonSendSync>>>,
}
use thread_cell::ThreadCell;
// ✅ Compiles — ThreadCell makes `NonSendSync` shareable across threads
assert_send::<ThreadCell<NonSendSync>>();
assert_sync::<ThreadCell<NonSendSync>>();
// ❌ Does not compile — Arc<Mutex<T>> requires T: Send
// assert_send::<Arc<std::sync::Mutex<NonSendSync>>>();
// assert_sync::<Arc<std::sync::Mutex<NonSendSync>>>();
use std::rc::Rc;
use std::cell::RefCell;
use thread_cell::ThreadCell;
#[derive(Debug)]
struct GameState {
score: usize,
}
fn main() {
// Rc<RefCell<_>> is !Send and !Sync
let shared = ThreadCell::new_with(|| Rc::new(RefCell::new(GameState { score: 0 })));
// synchronous access
shared.run_blocking(|state| {
state.borrow_mut().score += 10;
println!("Score after sync update: {}", state.borrow().score);
});
// async access - `tokio` feature flag
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on({
let shared = shared.clone();
async move {
let a = shared.clone();
let b = shared;
let t1 = tokio::spawn(async move {
a.run(|state| {
state.borrow_mut().score += 5;
state.borrow().score
})
.await
});
let t2 = tokio::spawn(async move {
b.run(|state| {
state.borrow_mut().score += 20;
state.borrow().score
})
.await
});
let (s1, s2) = tokio::join!(t1, t2);
println!("Task 1 score: {}", s1.unwrap());
println!("Task 2 score: {}", s2.unwrap());
}
});
let final_score = shared.run_blocking(|state| state.borrow().score);
println!("Final score: {final_score}");
}
Score after sync update: 10
Task 1 score: 15
Task 2 score: 35
Final score: 35
Each call with ThreadCell involves message passing to a background thread. This may be faster than a highly contended mutex for some workloads, but also may be slower for very fine-grained access patterns. If T: Send + Sync default to using a regular concurrent lock - Arc<Mutex<T>>.
When dealing with !Send types and still wanting to mutate it safely across threads, use ThreadCell.
Use case:
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::Arc;
use std::sync::RwLock;
struct NonSendSync {
parent: Option<Rc<RefCell<NonSendSync>>>,
children: Vec<Rc<RefCell<NonSendSync>>>,
}
struct SendSync {
parent: Option<Arc<RwLock<SendSync>>>,
children: Vec<Arc<RwLock<SendSync>>>,
}
Operations on a ThreadCell<NonSendSync> may be faster than operations on a Arc<Mutex<SendSync>>.
Another use case for ThreadCell is when code may be called from a sync or async context. There exists implementations for async Mutex (e.g. tokio::sync::Mutex) and sync Mutex (e.g. std::sync::Mutex) but usually one has to choose one or the other. Since ThreadCell uses message passing, code can be called from async or sync code depending on the context - e.g. ThreadCell::run(..).await vs ThreadCell::run_blocking(..). The closure can also execute async code with help of the provided run_local function.