Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ jobs:
run: rustup toolchain install ${{ matrix.toolchain }}
- name: Test on ${{ matrix.toolchain }}
shell: bash
run: cargo +${{ matrix.toolchain }} test
run: cargo +${{ matrix.toolchain }} test --all-features
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,12 @@ keywords = ["rc", "refcell", "smart-pointer", "interior-mutability", "thin"]
categories = ["data-structures", "memory-management"]
readme = "README.md"

[features]
weak = []

[dependencies]
synchrony = "0.1.7"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@ Unlike `RefCell` which supports multiple immutable borrows OR one mutable borrow

`try_borrow` is available for both versions, which returns `None` instead of panicking or blocking when already borrowed.

## Weak References (feature `weak`)

Enable the optional `weak` feature to get non-owning `Weak<T>` handles. A weak handle does not keep the value alive; the value is dropped when the last strong `ThinCell` is dropped, and the allocation is freed once the last `Weak` is dropped. Internally, the strong side holds one implicit weak reference like `Rc`/`Arc`. Use `ThinCell::downgrade` to create a `Weak<T>` and `Weak::upgrade` to try to regain a strong handle. Both `sync` and `unsync` versions expose `strong_count` and `weak_count` when the feature is on.

```rust
# #[cfg(feature = "weak")]
# {
use thin_cell::sync::ThinCell;

let cell = ThinCell::new(String::from("hello"));
let weak = cell.downgrade();

assert_eq!(cell.strong_count(), 1);
assert_eq!(cell.weak_count(), 2);

let strong = weak.upgrade().unwrap();
assert_eq!(strong.strong_count(), 2);

drop(cell);
drop(strong);
assert!(weak.upgrade().is_none()); // value already dropped
# }
```

## Examples

### Basic Usage
Expand Down
223 changes: 222 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
Expand Down Expand Up @@ -49,6 +50,21 @@ macro_rules! thin_cell {
state: &'a State,
}

/// A weak reference to a [`ThinCell`] that doesn't prevent dropping.
///
/// `Weak` references don't keep the value alive. The value will be dropped
/// when all strong references (`ThinCell`) are dropped, even if weak
/// references still exist.
///
/// Use [`Weak::upgrade`] to attempt to convert a weak reference back to a
/// strong reference (`ThinCell`). This will fail if the value has already
/// been dropped.
#[cfg(feature = "weak")]
pub struct Weak<T: ?Sized> {
ptr: NonNull<()>,
_marker: PhantomData<Inner<T>>,
}

impl<T> ThinCell<T> {
/// Creates a new `ThinCell` wrapping the given data.
pub fn new(data: T) -> Self {
Expand Down Expand Up @@ -84,13 +100,35 @@ macro_rules! thin_cell {
/// # Safety
/// The caller must guarantee that there are no other owners and it is not
/// currently borrowed.
#[cfg(not(feature = "weak"))]
pub unsafe fn unwrap_unchecked(self) -> T {
let this = ManuallyDrop::new(self);
// SAFETY: guaranteed by caller to have unique ownership and is not borrowed
let inner = unsafe { Box::from_raw(this.inner_ptr() as *mut Inner<T>) };

inner.data.into_inner()
}

/// Consumes the `ThinCell`, returning the inner value.
///
/// # Safety
/// The caller must guarantee that there are no other strong owners and it is not
/// currently borrowed.
#[cfg(feature = "weak")]
pub unsafe fn unwrap_unchecked(self) -> T {
let this = ManuallyDrop::new(self);
let inner = this.inner();

let _weak: Weak<T> = Weak {
ptr: this.ptr,
_marker: PhantomData,
};

// SAFETY: guaranteed by caller to have unique strong ownership and
// not be borrowed. This moves out `T` without touching the
// allocation so outstanding weak refs remain valid.
unsafe { std::ptr::read(inner.data.get()) }
}
}

impl<T: ?Sized> ThinCell<T> {
Expand Down Expand Up @@ -138,6 +176,7 @@ macro_rules! thin_cell {
/// # Safety
///
/// `self` must be the last owner and it must not be used after this call.
#[cfg(not(feature = "weak"))]
unsafe fn drop_in_place(&mut self) {
drop(unsafe { Box::from_raw(self.inner_ptr() as *mut Inner<T>) })
}
Expand Down Expand Up @@ -165,7 +204,7 @@ macro_rules! thin_cell {
}
}

/// Returns the number of owners.
/// Returns the number of strong owners.
pub fn count(&self) -> usize {
self.state().load().count()
}
Expand Down Expand Up @@ -351,6 +390,45 @@ macro_rules! thin_cell {
}
}

#[cfg(feature = "weak")]
impl<T: ?Sized> ThinCell<T> {
/// Returns the number of strong references.
pub fn strong_count(&self) -> usize {
self.state().load().strong_count()
}

/// Returns the number of weak references.
pub fn weak_count(&self) -> usize {
self.state().load().weak_count()
}

/// Creates a new weak reference to this `ThinCell`.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "weak")]
/// # {
/// # use thin_cell::sync::ThinCell;
/// let cell = ThinCell::new(42);
/// let weak = cell.downgrade();
///
/// assert_eq!(cell.strong_count(), 1);
/// assert_eq!(cell.weak_count(), 2);
///
/// drop(cell);
/// assert!(weak.upgrade().is_none());
/// # }
/// ```
pub fn downgrade(&self) -> Weak<T> {
self.state().inc_weak();
Weak {
ptr: self.ptr,
_marker: PhantomData,
}
}
}

impl<T, const N: usize> ThinCell<[T; N]> {
/// Coerce an array [`ThinCell`] to a slice one.
pub fn unsize_slice(self) -> ThinCell<[T]> {
Expand Down Expand Up @@ -454,6 +532,7 @@ macro_rules! thin_cell {
}
}

#[cfg(not(feature = "weak"))]
impl<T: ?Sized> Drop for ThinCell<T> {
fn drop(&mut self) {
let inner = self.inner();
Expand All @@ -470,6 +549,148 @@ macro_rules! thin_cell {
}
}

#[cfg(feature = "weak")]
impl<T: ?Sized> Drop for ThinCell<T> {
fn drop(&mut self) {
let inner = self.inner();
if !inner.state.dec() {
// Not last strong owner, nothing to do
return;
}

// Keep the strong side's collective weak ref alive as a real `Weak`
// guard so unwinding through `T::drop` still releases the allocation.
let _weak: Weak<T> = Weak {
ptr: self.ptr,
_marker: PhantomData,
};

// Drop the value in place.
// SAFETY: We are the last strong owner, so we have unique ownership.
unsafe {
std::ptr::drop_in_place(inner.data.get());
}
}
}

#[cfg(feature = "weak")]
impl<T: ?Sized> Weak<T> {
/// Reconstructs the raw pointer to the inner allocation.
fn inner_ptr(&self) -> *const Inner<T> {
let ptr = self.ptr.as_ptr();

if ThinCell::<T>::IS_SIZED {
// SIZED CASE: Cast pointer-to-pointer
let ptr_ref = &ptr as *const *mut () as *const *const Inner<T>;
unsafe { *ptr_ref }
} else {
// UNSIZED CASE: Read metadata
let metadata = unsafe { *(ptr as *const usize) };
FatPtr { ptr, metadata }.into_ptr()
}
}

/// Returns a reference to the inner allocation.
fn inner(&self) -> &Inner<T> {
unsafe { &*self.inner_ptr() }
}

/// Returns a reference to the state cell.
fn state(&self) -> &State {
&self.inner().state
}

/// Attempts to upgrade the weak reference to a strong reference.
///
/// Returns `Some(ThinCell)` if the value still exists, or `None` if it
/// has been dropped.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "weak")]
/// # {
/// # use thin_cell::sync::ThinCell;
/// let cell = ThinCell::new(42);
/// let weak = cell.downgrade();
///
/// let strong = weak.upgrade().unwrap();
/// assert_eq!(*strong.borrow(), 42);
///
/// drop(cell);
/// drop(strong);
/// assert!(weak.upgrade().is_none());
/// # }
/// ```
pub fn upgrade(&self) -> Option<ThinCell<T>> {
let state = self.state();

// Atomically try to increment strong count only if non-zero
if !state.try_inc() {
return None;
}

Some(ThinCell {
ptr: self.ptr,
_marker: PhantomData,
})
}

/// Returns the number of strong references.
pub fn strong_count(&self) -> usize {
self.state().load().strong_count()
}

/// Returns the number of weak references.
pub fn weak_count(&self) -> usize {
self.state().load().weak_count()
}

/// Gets a raw pointer to the inner allocation.
///
/// The pointer is valid only if the strong count is non-zero.
pub fn as_ptr(&self) -> *const () {
self.ptr.as_ptr()
}
}

#[cfg(feature = "weak")]
impl<T: ?Sized> Clone for Weak<T> {
fn clone(&self) -> Self {
self.state().inc_weak();
Weak {
ptr: self.ptr,
_marker: PhantomData,
}
}
}

#[cfg(feature = "weak")]
impl<T: ?Sized> Drop for Weak<T> {
fn drop(&mut self) {
let inner = self.inner();
if !inner.state.dec_weak() {
// Not last weak ref, nothing to do
return;
}

// Last weak ref and no strong refs - deallocate memory only
// The value T was already dropped when the last strong ref was dropped
// SAFETY: We are the last weak owner and no strong owners exist
unsafe {
let layout = std::alloc::Layout::for_value(inner);
std::alloc::dealloc(self.inner_ptr() as *mut u8, layout);
}
}
}

#[cfg(feature = "weak")]
impl<T: ?Sized> Debug for Weak<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(Weak)")
}
}

impl<T: Default> Default for ThinCell<T> {
fn default() -> Self {
ThinCell::new(T::default())
Expand Down
Loading
Loading