use std::cell::UnsafeCell;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use std::ptr::NonNull;
#[cfg_attr(not(feature = "shared-owned-debug"), repr(transparent))]
pub struct Owned<T> {
inner: Option<NonNull<UnsafeCell<T>>>,
#[cfg(feature = "shared-owned-debug")]
safety: NonNull<AtomicBool>,
}
impl<T> Owned<T> {
pub fn new(inner: T) -> Self {
Self {
inner: NonNull::new(Box::into_raw(Box::new(UnsafeCell::new(inner)))),
#[cfg(feature = "shared-owned-debug")]
safety: {
let boxed = Box::new(AtomicBool::new(true));
let safety = NonNull::from_ref(&*boxed);
let _ = Box::leak(boxed);
safety
},
}
}
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub const fn share(&self) -> Shared<T> {
Shared {
ptr: self
.inner
.expect("Inner will only turn into a none on drop."),
#[cfg(feature = "shared-owned-debug")]
safety: self.safety,
}
}
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub const unsafe fn get(&self) -> &T {
unsafe {
&*self
.inner
.as_ref()
.expect("Inner will only turn into a none on drop.")
.as_ref()
.get()
}
}
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub const unsafe fn get_mut(&mut self) -> &mut T {
unsafe {
self.inner
.as_mut()
.expect("Inner will only turn into a none on drop.")
.as_mut()
.get_mut()
}
}
#[must_use]
pub fn derived(&self, shared: &Shared<T>) -> bool {
self.share().same_origin(shared)
}
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn into_inner(mut self) -> T {
unsafe {
Box::from_raw(
self.inner
.take()
.expect("Inner will only turn into a none on drop.")
.as_ptr(),
)
}
.into_inner()
}
}
impl<T> Drop for Owned<T> {
fn drop(&mut self) {
#[cfg(feature = "shared-owned-debug")]
unsafe { self.safety.as_ref() }.store(false, std::sync::atomic::Ordering::SeqCst);
if let Some(ptr) = self.inner.take() {
let _ = unsafe { Box::from_raw(ptr.as_ptr()) };
}
}
}
#[cfg_attr(not(feature = "shared-owned-debug"), repr(transparent))]
pub struct Shared<T> {
ptr: NonNull<UnsafeCell<T>>,
#[cfg(feature = "shared-owned-debug")]
safety: NonNull<AtomicBool>,
}
impl<T> Clone for Shared<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Shared<T> {}
impl<T> Hash for Shared<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ptr.addr().hash(state);
}
}
impl<T> PartialEq for Shared<T> {
fn eq(&self, other: &Self) -> bool {
self.ptr.as_ptr().addr() == other.ptr.as_ptr().addr()
}
}
impl<T> Eq for Shared<T> {}
impl<T> PartialOrd for Shared<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for Shared<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.ptr.addr().cmp(&other.ptr.addr())
}
}
impl<T> Shared<T> {
#[must_use]
#[track_caller]
#[const_fn::const_fn(cfg(not(feature = "shared-owned-debug")))]
pub const unsafe fn get(&self) -> &T {
#[cfg(feature = "shared-owned-debug")]
if !self.is_alive() {
panic!("Tried to read deallocated shared value");
}
unsafe { &*self.ptr.as_ref().get() }
}
#[must_use]
#[track_caller]
#[const_fn::const_fn(cfg(not(feature = "shared-owned-debug")))]
pub const unsafe fn get_mut(&mut self) -> &mut T {
#[cfg(feature = "shared-owned-debug")]
if !self.is_alive() {
panic!("Tried to read deallocated shared value");
}
unsafe { self.ptr.as_mut() }.get_mut()
}
#[must_use]
pub fn same_origin(&self, other: &Self) -> bool {
self.ptr.as_ptr() == other.ptr.as_ptr()
}
#[must_use]
#[cfg(feature = "shared-owned-debug")]
pub fn is_alive(&self) -> bool {
unsafe { self.safety.as_ref() }.load(std::sync::atomic::Ordering::SeqCst)
}
}
#[cfg(test)]
mod owned_shared_tests {
use super::Owned;
use droptest::{DropRegistry, assert_drop, assert_no_drop};
#[test]
pub fn elements_are_dropped() {
let registry = DropRegistry::default();
let guard = registry.new_guard_for(420_usize);
let id = guard.id();
let owned = Owned::new(guard);
assert_no_drop!(
registry,
id,
"Creating an owned version from a value somehow dropped it?"
);
let shared = owned.share();
assert_eq!(
*unsafe { shared.get() }.value(),
420_usize,
"The wrong value was in the shared value"
);
assert_no_drop!(
registry,
id,
"The owned value got dropped while creating a shared value."
);
drop(owned);
assert_drop!(
registry,
id,
"The value still exists, even though it should have been dropped."
);
}
}