| Crates.io | send_cells |
| lib.rs | send_cells |
| version | 0.2.0 |
| created_at | 2025-01-06 07:20:47.80787+00 |
| updated_at | 2025-08-13 02:49:56.884963+00 |
| description | Safe and unsafe cells implementing Send/Sync |
| homepage | https://sealedabstract.com/code/send_cells |
| repository | https://github.com/drewcrawford/send_cells |
| max_upload_size | |
| id | 1505370 |
| size | 324,314 |
Thread-safe cell types for sending and sharing non-Send/non-Sync types across thread boundaries.

This crate provides specialized cell types that allow you to work with types that don't normally
implement Send or Sync traits, enabling their use in concurrent contexts while maintaining
memory safety through either runtime checks or manual verification.
The send_cells crate offers two categories of wrappers:
This crate may be considered an alternative to the fragile crate, but provides a more ergonomic API and additional unsafe variants for maximum performance.
use send_cells::{SendCell, SyncCell};
use std::rc::Rc;
use std::sync::Arc;
use std::thread;
// Wrap a non-Send type to make it Send
let data = Rc::new(42);
let send_cell = SendCell::new(data);
// Access is checked at runtime - panics if accessed from wrong thread
assert_eq!(**send_cell.get(), 42);
// Wrap a non-Sync type to make it Sync
let shared_data = std::cell::RefCell::new("shared");
let sync_cell = Arc::new(SyncCell::new(shared_data));
// Share between threads with automatic synchronization
let sync_clone = Arc::clone(&sync_cell);
thread::spawn(move || {
sync_clone.with(|data| {
println!("Data: {}", data.borrow());
});
}).join().unwrap();
Safe wrappers provide runtime-checked access to wrapped values:
SendCell<T>Allows sending non-Send types between threads with runtime thread checking:
SyncCell<T>Allows sharing non-Sync types between threads with mutex-based synchronization:
SendFuture<T>Wraps non-Send futures to make them Send:
Unsafe wrappers provide zero-cost abstractions when you can manually verify safety:
UnsafeSendCell<T>Allows sending non-Send types without runtime checks:
unsafe blocks for all accessUnsafeSendFuture<T>Wraps non-Send futures without runtime checks:
| Type | Use When | Performance | Safety |
|---|---|---|---|
SendCell |
Moving non-Send types in async contexts | Good | Runtime checked |
SyncCell |
Sharing non-Sync types between threads | Good | Mutex protected |
SendFuture |
Using non-Send futures with Send requirements | Good | Runtime checked |
UnsafeSendCell |
Platform guarantees thread safety | Best | Manual verification |
UnsafeSendFuture |
Maximum performance for futures | Best | Manual verification |
Full support for all major platforms with standard library support.
This crate has full wasm32-unknown-unknown support with runtime thread checks
for web workers. Thread IDs are properly tracked even in WASM environments.
use send_cells::SendCell;
use std::rc::Rc;
async fn process_data() {
// Rc is not Send, but we need to use it in an async context
let data = Rc::new(vec![1, 2, 3]);
let cell = SendCell::new(data);
// Can be moved into async blocks that might run on different threads
// Note: This would panic if actually polled on a different thread!
let task = async move {
// Will panic if actually polled on a different thread
let data = cell.get();
data.iter().sum::<i32>()
};
// In a real application with tokio:
// let result = tokio::spawn(task).await.unwrap();
}
use send_cells::SyncCell;
use std::cell::RefCell;
use std::sync::Arc;
use std::thread;
let counter = RefCell::new(0);
let sync_counter = Arc::new(SyncCell::new(counter));
let mut handles = vec![];
for _ in 0..10 {
let counter_clone = Arc::clone(&sync_counter);
handles.push(thread::spawn(move || {
counter_clone.with_mut(|counter| {
*counter.borrow_mut() += 1;
});
}));
}
for handle in handles {
handle.join().unwrap();
}
sync_counter.with(|counter| {
assert_eq!(*counter.borrow(), 10);
});
use send_cells::UnsafeSendCell;
use std::rc::Rc;
// Platform API guarantees callbacks run on main thread
fn setup_main_thread_callback() {
let data = Rc::new("main thread only");
// SAFETY: Platform guarantees this callback runs on main thread
let cell = unsafe { UnsafeSendCell::new_unchecked(data) };
platform_specific_api(move || {
// SAFETY: We're guaranteed to be on the main thread
let data = unsafe { cell.get() };
println!("Callback data: {}", data);
});
}
The safe wrappers (SendCell, SyncCell, SendFuture) provide memory safety through:
The unsafe wrappers require manual verification of:
Always prefer safe wrappers unless you have specific performance requirements and can rigorously verify thread safety.
ThreadId + wrapped valueMutex<()> + wrapped value