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.
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);
}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);
});
});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();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.