1 unstable release
Uses new Rust 2024
new 0.1.0 | Mar 23, 2025 |
---|
#849 in Rust patterns
13KB
101 lines
unchecked_wrap
This crate provides the UncheckedSync<T>
, UncheckedSend<T>
, and
UncheckedSyncSend<T>
types. They are transparent wrappers around T
, except
that they unconditionally implement Sync
, Send
, or both Sync
and Send
,
respectively. They can be used for, e.g., a static mut
replacement, sending
pointers over thread boundaries, storing raw pointers in thread-safe
abstractions, etc.
Constructing these wrappers is unsafe. However, the Unchecked
wrapper types
implement Deref
and DerefMut
, making them mostly usable as if they're just
plain values of type T
.
UncheckedSync<T>
, UncheckedSend<T>
, and UncheckedSyncSend<T>
are all
guaranteed to have the same size, alignment, and ABI as T
.
Examples
A static mut
replacement
Generally, global mutable state should be avoided. However, sometimes it is the right tool. This crate makes this usage pattern more convenient.
mod my_module {
use std::cell::Cell;
use unchecked_wrap::UncheckedSync;
// SAFETY: Only accessed via functions in this module that should be
// used only in a single thread
static THING: UncheckedSync<Cell<u64>> = unsafe { UncheckedSync::new(Cell::new(0)) };
/// # Safety
/// Must be called in the same thread as other functions in this module.
pub unsafe fn increment() {
THING.set(THING.get() + 1);
}
/// # Safety
/// Must be called in the same thread as other functions in this module.
pub unsafe fn get() -> u64 {
THING.get()
}
}
// SAFETY: This doctest is single-threaded.
unsafe {
my_module::increment();
my_module::increment();
assert_eq!(my_module::get(), 2);
}
Sending a raw pointer across thread boundaries
use unchecked_wrap::UncheckedSend;
let x = 123;
// Suppose that there's some reason that needs to be a raw pointer.
// SAFETY: A raw pointer doesn't actually have thread-safety invariants.
let ptr = unsafe { UncheckedSend::new(&raw const x) };
std::thread::scope(|scope| {
scope.spawn(move || {
// SAFETY: `x` is not deallocated yet, and is not modified
assert_eq!(unsafe { **ptr }, 123);
});
scope.spawn(move || {
// SAFETY: `x` is not deallocated yet, and is not modified
assert_eq!(unsafe { **ptr }, 123);
});
});
Storing a raw pointer in a thread-safe abstraction
use std::marker::PhantomData;
use std::ptr::NonNull;
use unchecked_wrap::UncheckedSyncSend;
struct MyBox<T> {
// We use UncheckedSyncSend to ignore the auto traits from NonNull,
// then we use PhantomData to get back the correct auto trait impls.
// That is, MyBox<T> implements Send/Sync iff T implements Send/Sync.
ptr: UncheckedSyncSend<NonNull<T>>,
_phantom: PhantomData<T>,
}
// No need for error-prone implementations of Send and Sync.
impl<T> MyBox<T> {
fn new(value: T) -> Self {
let ptr = NonNull::new(Box::into_raw(Box::new(value))).unwrap();
Self {
// SAFETY: A MyBox<T> is treated as if it owns T,
ptr: unsafe { UncheckedSyncSend::new(ptr) },
_phantom: PhantomData,
}
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
let ptr = *self.ptr;
// SAFETY: ptr was allocated via a Box
unsafe {
drop(Box::from_raw(ptr.as_ptr()));
}
}
}
let a_box = MyBox::new(String::from("abc"));
let join_handle = std::thread::spawn(move || {
drop(a_box);
});
join_handle.join().unwrap();
A note on trait implementations
UncheckedSync
, UncheckedSend
, and UncheckedSyncSend
do not implement
common traits such as Debug
or Hash
This is because, if, for example, UncheckedSync<T>
were to implement Debug
by forwarding to the Debug
implementation of the T
inside, and if a struct
were to store an UncheckedSync
/UncheckedSend
, and a #[derive(Debug)]
were
to be applied to the struct, then the automatically-generated Debug
impl might
read the T
value inside in a way that violates thread-safety. (There was
previously an unsoundness in std due to a similar issue with
ManuallyDrop
.) In order to avoid this footgun, the Unchecked
wrappers do not implement such traits at all.
However, UncheckedSync<T>
, UncheckedSend<T>
, and UncheckedSyncSend<T>
each
implement Copy
when T: Copy
. This is to facilitate storing raw pointers in
contexts where they are known to be thread-safe, and then easily copy them
around. I believe that Copy
is unlikely to be a footgun in the aforementioned
way.