use core::fmt;
use core::mem::ManuallyDrop;
use core::ops::Deref;
pub struct MainThreadBound<T> {
#[cfg(feature = "std")]
owner: std::thread::ThreadId,
value: ManuallyDrop<T>,
}
#[allow(
clippy::non_send_fields_in_send_ty,
reason = "`MainThreadBound` deliberately carries non-`Send` data; the `Send` impl is sound because access is confined to the owning thread by `assert_owner`"
)]
unsafe impl<T> Send for MainThreadBound<T> {}
unsafe impl<T> Sync for MainThreadBound<T> {}
impl<T> MainThreadBound<T> {
#[cfg(feature = "std")]
#[must_use]
pub fn new(value: T) -> Self {
Self {
owner: std::thread::current().id(),
value: ManuallyDrop::new(value),
}
}
#[cfg(not(feature = "std"))]
#[must_use]
pub const fn new(value: T) -> Self {
Self {
value: ManuallyDrop::new(value),
}
}
#[cfg(feature = "std")]
#[inline]
fn is_owner_thread(&self) -> bool {
std::thread::current().id() == self.owner
}
#[cfg(not(feature = "std"))]
#[inline]
#[allow(clippy::unused_self)]
const fn is_owner_thread(&self) -> bool {
true
}
#[inline]
fn assert_owner(&self) {
assert!(
self.is_owner_thread(),
"MainThreadBound accessed off the main thread: a value confined to \
the main / UI thread escaped to a worker."
);
}
#[inline]
#[must_use]
pub fn into_inner(self) -> T {
self.assert_owner();
let mut me = ManuallyDrop::new(self);
unsafe { ManuallyDrop::take(&mut me.value) }
}
}
impl<T> Deref for MainThreadBound<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
self.assert_owner();
&self.value
}
}
impl<T> core::ops::DerefMut for MainThreadBound<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
self.assert_owner();
&mut self.value
}
}
impl<T> Drop for MainThreadBound<T> {
fn drop(&mut self) {
if self.is_owner_thread() {
unsafe { ManuallyDrop::drop(&mut self.value) }
} else {
debug_assert!(
false,
"MainThreadBound dropped off the main thread; leaking the inner value \
to stay sound"
);
}
}
}
impl<T> fmt::Debug for MainThreadBound<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MainThreadBound").finish_non_exhaustive()
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::MainThreadBound;
use alloc::rc::Rc;
#[test]
fn access_on_owner_thread_succeeds() {
let bound = MainThreadBound::new(Rc::new(7u32));
assert_eq!(**bound, 7);
assert_eq!(*bound.into_inner(), 7);
}
#[test]
fn is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<MainThreadBound<Rc<u32>>>();
}
#[test]
fn access_off_owner_thread_panics() {
let bound = MainThreadBound::new(Rc::new(1u32));
let handle = std::thread::spawn(move || {
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = &*bound;
}));
core::mem::forget(bound);
caught.is_err()
});
assert!(
handle.join().expect("spawned thread panicked unexpectedly"),
"accessing MainThreadBound off the owner thread must panic"
);
}
}