From 9dbd07f9737d0a6d87d8b844e186205d61212faf Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Thu, 10 Jul 2025 06:45:20 +0300 Subject: [PATCH 1/9] Part 1: re-implement heap storage in terms of a pluggable storage model. --- src/errors.rs | 46 +++++++----- src/lib.rs | 190 +++++++++++++++++++++++++++---------------------- src/storage.rs | 128 +++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+), 104 deletions(-) create mode 100644 src/storage.rs diff --git a/src/errors.rs b/src/errors.rs index a366ce3..b11cfb0 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,48 +1,56 @@ -use super::{dealloc, Channel}; +use crate::Channel; +use crate::Global; +use crate::Storage; + use core::fmt; +use core::marker::PhantomData; use core::mem; -use core::ptr::NonNull; /// An error returned when trying to send on a closed channel. Returned from /// [`Sender::send`](crate::Sender::send) if the corresponding [`Receiver`](crate::Receiver) /// has already been dropped. /// /// The message that could not be sent can be retreived again with [`SendError::into_inner`]. -pub struct SendError { - channel_ptr: NonNull>, +pub struct SendError = Global> { + storage: S, + + _t: PhantomData, } -unsafe impl Send for SendError {} -unsafe impl Sync for SendError {} +unsafe impl> Send for SendError {} +unsafe impl> Sync for SendError {} -impl SendError { +impl> SendError { /// # Safety /// /// By calling this function, the caller semantically transfers ownership of the /// channel's resources to the created `SendError`. Thus the caller must ensure that the /// pointer is not used in a way which would violate this ownership transfer. Moreover, /// the caller must assert that the channel contains a valid, initialized message. - pub(crate) const unsafe fn new(channel_ptr: NonNull>) -> Self { - Self { channel_ptr } + pub(crate) const unsafe fn new(storage: S) -> Self { + Self { + storage, + _t: PhantomData, + } } /// Consumes the error and returns the message that failed to be sent. #[inline] pub fn into_inner(self) -> T { - let channel_ptr = self.channel_ptr; + let mut storage = self.storage.clone(); // Don't run destructor if we consumed ourselves. Freeing happens here. mem::forget(self); // SAFETY: we have ownership of the channel - let channel: &Channel = unsafe { channel_ptr.as_ref() }; + let channel: &Channel = unsafe { storage.as_ref() }; // SAFETY: we know that the message is initialized according to the safety requirements of // `new` let message = unsafe { channel.take_message() }; // SAFETY: we own the channel - unsafe { dealloc(channel_ptr) }; + unsafe { storage.release() }; message } @@ -50,35 +58,35 @@ impl SendError { /// Get a reference to the message that failed to be sent. #[inline] pub fn as_inner(&self) -> &T { - unsafe { self.channel_ptr.as_ref().message().assume_init_ref() } + unsafe { self.storage.as_ref().message().assume_init_ref() } } } -impl Drop for SendError { +impl> Drop for SendError { fn drop(&mut self) { // SAFETY: we have ownership of the channel and require that the message is initialized // upon construction unsafe { - self.channel_ptr.as_ref().drop_message(); - dealloc(self.channel_ptr); + self.storage.as_ref().drop_message(); + self.storage.release(); } } } -impl fmt::Display for SendError { +impl> fmt::Display for SendError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { "sending on a closed channel".fmt(f) } } -impl fmt::Debug for SendError { +impl> fmt::Debug for SendError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "SendError<{}>(_)", stringify!(T)) } } #[cfg(feature = "std")] -impl std::error::Error for SendError {} +impl> std::error::Error for SendError {} /// An error returned from receiving methods that block/wait until a message is available. /// diff --git a/src/lib.rs b/src/lib.rs index b54fcd6..edd868e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,6 +125,9 @@ #[cfg(not(oneshot_loom))] extern crate alloc; +mod storage; +pub use storage::*; + use core::{ marker::PhantomData, mem::{self, MaybeUninit}, @@ -176,11 +179,7 @@ mod thread { } #[cfg(oneshot_loom)] -mod loombox; -#[cfg(not(oneshot_loom))] -use alloc::boxed::Box; -#[cfg(oneshot_loom)] -use loombox::Box; +pub(crate) mod loombox; mod errors; // Wildcard imports are not nice. But since multiple errors have various conditional compilation, @@ -189,17 +188,19 @@ pub use errors::*; /// Creates a new oneshot channel and returns the two endpoints, [`Sender`] and [`Receiver`]. pub fn channel() -> (Sender, Receiver) { - // Allocate the channel on the heap and get the pointer. - // The last endpoint of the channel to be alive is responsible for freeing the channel - // and dropping any object that might have been written to it. - let channel_ptr = NonNull::from(Box::leak(Box::new(Channel::new()))); + // SAFETY: This implicitly calls `initialize()` on the storage, so we are not allowed + // to call it again. OK, we do not. + let storage = unsafe { Global::::new() }; ( Sender { - channel_ptr, + storage: storage.clone(), + _invariant: PhantomData, + }, + Receiver { + storage, _invariant: PhantomData, }, - Receiver { channel_ptr }, ) } @@ -209,8 +210,9 @@ pub fn channel() -> (Sender, Receiver) { /// /// Can be used to send a message to the corresponding [`Receiver`]. #[derive(Debug)] -pub struct Sender { - channel_ptr: NonNull>, +pub struct Sender = Global> { + storage: S, + // In reality we want contravariance, however we can't obtain that. // // Consider the following scenario: @@ -240,23 +242,25 @@ pub struct Sender { /// This type implement [`IntoFuture`](core::future::IntoFuture) when the `async` feature is enabled. /// This allows awaiting it directly in an async context. #[derive(Debug)] -pub struct Receiver { +pub struct Receiver = Global> { + storage: S, + // Covariance is the right choice here. Consider the example presented in Sender, and you'll // see that if we replaced `rx` instead then we would get the expected behavior - channel_ptr: NonNull>, + _invariant: PhantomData, } -unsafe impl Send for Sender {} +unsafe impl> Send for Sender {} // SAFETY: The only methods that assumes there is only a single reference to the sender // takes `self` by value, guaranteeing that there is only one reference to the sender at // the time it is called. -unsafe impl Sync for Sender {} +unsafe impl> Sync for Sender {} -unsafe impl Send for Receiver {} -impl Unpin for Receiver {} +unsafe impl> Send for Receiver {} +impl> Unpin for Receiver {} -impl Sender { +impl> Sender { /// Sends `message` over the channel to the corresponding [`Receiver`]. /// /// Returns an error if the receiver has already been dropped. The message can @@ -270,16 +274,18 @@ impl Sender { /// depends on your executor. If this method returns a `SendError`, please mind that dropping /// the error involves running any drop implementation on the message type, and freeing the /// channel's heap allocation, which might or might not be lock-free. - pub fn send(self, message: T) -> Result<(), SendError> { - let channel_ptr = self.channel_ptr; + pub fn send(self, message: T) -> Result<(), SendError> { + let storage = self.storage.clone(); // Don't run our Drop implementation if send was called, any cleanup now happens here mem::forget(self); - // SAFETY: The channel exists on the heap for the entire duration of this method and we - // only ever acquire shared references to it. Note that if the receiver disconnects it - // does not free the channel. - let channel = unsafe { channel_ptr.as_ref() }; + // SAFETY: The storage trait requires us to only `release()` once per family of clones. + // By forgetting self, we ensure that `Drop` does not call it, so now it is between the + // receiver and the sender. If the receiver disconnects before we fill the channel, it + // does not release it, therefore we know that either we fill the channel and the receiver + // will later clean it up or we detect here that there is nobody listening and release it. + let channel = unsafe { storage.as_ref() }; // Write the message into the channel on the heap. // SAFETY: The receiver only ever accesses this memory location if we are in the MESSAGE @@ -332,11 +338,11 @@ impl Sender { Ok(()) } // The receiver was already dropped. The error is responsible for freeing the channel. - // SAFETY: since the receiver disconnected it will no longer access `channel_ptr`, so + // SAFETY: since the receiver disconnected it will no longer access the storage, so // we can transfer exclusive ownership of the channel's resources to the error. // Moreover, since we just placed the message in the channel, the channel contains a // valid message. - DISCONNECTED => Err(unsafe { SendError::new(channel_ptr) }), + DISCONNECTED => Err(unsafe { SendError::new(storage) }), _ => unreachable!(), } } @@ -345,24 +351,26 @@ impl Sender { /// /// If true is returned, a future call to send is guaranteed to return an error. pub fn is_closed(&self) -> bool { - // SAFETY: The channel exists on the heap for the entire duration of this method and we - // only ever acquire shared references to it. Note that if the receiver disconnects it - // does not free the channel. - let channel = unsafe { self.channel_ptr.as_ref() }; + // SAFETY: If the receiver disconnects before the sender, it does not release the channel, + // so we know that the storage is still valid. Therefore, if it is even possible to call + // this method, we know the storage must be valid for the duration of this method. + let channel = unsafe { self.storage.as_ref() }; // ORDERING: We *chose* a Relaxed ordering here as it sufficient to // enforce the method's contract: "if true is returned, a future // call to send is guaranteed to return an error." channel.state.load(Relaxed) == DISCONNECTED } +} +impl Sender> { /// Consumes the Sender, returning a raw pointer to the channel on the heap. /// /// This is intended to simplify using oneshot channels with some FFI code. The only safe thing /// to do with the returned pointer is to later reconstruct the Sender with [Sender::from_raw]. /// Memory will leak if the Sender is never reconstructed. pub fn into_raw(self) -> *mut () { - let raw = self.channel_ptr.as_ptr() as *mut (); + let raw = self.storage.to_raw().as_ptr() as *mut (); mem::forget(self); raw } @@ -376,13 +384,13 @@ impl Sender { /// Constructing multiple Senders from the same raw pointer leads to undefined behavior. pub unsafe fn from_raw(raw: *mut ()) -> Self { Self { - channel_ptr: NonNull::new_unchecked(raw as *mut Channel), + storage: Global::from_raw(NonNull::new_unchecked(raw as *mut Channel)), _invariant: PhantomData, } } } -impl Drop for Sender { +impl> Drop for Sender { fn drop(&mut self) { // SAFETY: The receiver only ever frees the channel if we are in the MESSAGE or // DISCONNECTED states. If we are in the MESSAGE state, then we called @@ -390,7 +398,7 @@ impl Drop for Sender { // DISCONNECTED state, then the receiver either received a MESSAGE so this statement is // unreachable, or was dropped and observed that our side was still alive, and thus didn't // free the channel. - let channel = unsafe { self.channel_ptr.as_ref() }; + let channel = unsafe { self.storage.as_ref() }; // Set the channel state to disconnected and read what state the receiver was in // ORDERING: we don't need release ordering here since there are no modifications we @@ -420,20 +428,22 @@ impl Drop for Sender { // happens-before unparking the receiver. waker.unpark(); } - // The receiver was already dropped. We are responsible for freeing the channel. + // The receiver was already dropped. We are responsible for releasing the channel storage. DISCONNECTED => { // SAFETY: when the receiver switches the state to DISCONNECTED they have received // the message or will no longer be trying to receive the message, and have // observed that the sender is still alive, meaning that we're responsible for - // freeing the channel allocation. - unsafe { dealloc(self.channel_ptr) }; + // releasing the channel storage. + unsafe { + self.storage.release(); + } } _ => unreachable!(), } } } -impl Receiver { +impl> Receiver { /// Checks if there is a message in the channel without blocking. Returns: /// * `Ok(message)` if there was a message in the channel. /// * `Err(Empty)` if the [`Sender`] is alive, but has not yet sent a message. @@ -448,8 +458,8 @@ impl Receiver { /// performs one atomic integer store and copies the message from the heap to the stack for /// returning it. pub fn try_recv(&self) -> Result { - // SAFETY: The channel will not be freed while this method is still running. - let channel = unsafe { self.channel_ptr.as_ref() }; + // SAFETY: The channel will not be released while this method is still running. + let channel = unsafe { self.storage.as_ref() }; // ORDERING: we use acquire ordering to synchronize with the store of the message. match channel.state.load(Acquire) { @@ -495,7 +505,7 @@ impl Receiver { // self, and this function does not exit until the message has been received or both side // of the channel are inactive and cleaned up. - let channel_ptr = self.channel_ptr; + let mut storage = self.storage.clone(); // Don't run our Drop implementation. This consuming recv method is responsible for freeing. mem::forget(self); @@ -503,8 +513,8 @@ impl Receiver { // SAFETY: the existence of the `self` parameter serves as a certificate that the receiver // is still alive, meaning that even if the sender was dropped then it would have observed // the fact that we're still alive and left the responsibility of deallocating the - // channel to us, so channel_ptr is valid - let channel = unsafe { channel_ptr.as_ref() }; + // channel to us, so the stored channel is valid + let channel = unsafe { storage.as_ref() }; // ORDERING: we use acquire ordering to synchronize with the write of the message in the // case that it's available @@ -542,17 +552,21 @@ impl Receiver { // SAFETY: we are in the message state so the message is valid let message = unsafe { channel.take_message() }; - // SAFETY: the Sender delegates the responsibility of deallocating - // the channel to us upon sending the message - unsafe { dealloc(channel_ptr) }; + // SAFETY: the Sender delegates the responsibility of releasing + // the storage to us upon sending the message + unsafe { + storage.release(); + }; break Ok(message); } // The sender was dropped while we were parked. DISCONNECTED => { - // SAFETY: the Sender doesn't deallocate the channel allocation in + // SAFETY: the Sender doesn't release the channel storage in // its drop implementation if we're receiving - unsafe { dealloc(channel_ptr) }; + unsafe { + storage.release(); + }; break Err(RecvError); } @@ -576,9 +590,11 @@ impl Receiver { // SAFETY: we are in the message state so the message is valid let message = unsafe { channel.take_message() }; - // SAFETY: the Sender delegates the responsibility of deallocating the + // SAFETY: the Sender delegates the responsibility of releasing the // channel to us upon sending the message - unsafe { dealloc(channel_ptr) }; + unsafe { + storage.release(); + }; Ok(message) } @@ -589,9 +605,11 @@ impl Receiver { // need to drop it. unsafe { channel.drop_waker() }; - // SAFETY: the sender does not deallocate the channel if it switches from - // empty to disconnected so we need to free the allocation - unsafe { dealloc(channel_ptr) }; + // SAFETY: the sender does not release the channel if it switches from + // empty to disconnected so we need to release the storage. + unsafe { + storage.release(); + }; Err(RecvError) } @@ -605,15 +623,19 @@ impl Receiver { // SAFETY: we are already in the message state so the sender has been forgotten // and it's our job to clean up resources - unsafe { dealloc(channel_ptr) }; + unsafe { + storage.release(); + }; Ok(message) } // The sender was dropped before sending anything, or we already received the message. DISCONNECTED => { - // SAFETY: the sender does not deallocate the channel if it switches from empty to - // disconnected so we need to free the allocation - unsafe { dealloc(channel_ptr) }; + // SAFETY: the sender does not release the channel if it switches from empty to + // disconnected so we need to release the storage. + unsafe { + storage.release(); + }; Err(RecvError) } @@ -803,9 +825,9 @@ impl Receiver { pub fn is_closed(&self) -> bool { // SAFETY: the existence of the `self` parameter serves as a certificate that the receiver // is still alive, meaning that even if the sender was dropped then it would have observed - // the fact that we're still alive and left the responsibility of deallocating the - // channel to us, so `self.channel` is valid - let channel = unsafe { self.channel_ptr.as_ref() }; + // the fact that we're still alive and left the responsibility of releasing the + // channel storage to us, so the channel in `self.storage` is valid + let channel = unsafe { self.storage.as_ref() }; // ORDERING: We *chose* a Relaxed ordering here as it is sufficient to // enforce the method's contract. Once true has been observed, it will remain true. @@ -821,9 +843,9 @@ impl Receiver { pub fn has_message(&self) -> bool { // SAFETY: the existence of the `self` parameter serves as a certificate that the receiver // is still alive, meaning that even if the sender was dropped then it would have observed - // the fact that we're still alive and left the responsibility of deallocating the - // channel to us, so `self.channel` is valid - let channel = unsafe { self.channel_ptr.as_ref() }; + // the fact that we're still alive and left the responsibility of releasing the + // channel storage to us, so the channel in `self.storage` is valid + let channel = unsafe { self.storage.as_ref() }; // ORDERING: An acquire ordering is used to guarantee no subsequent loads is reordered // before this one. This upholds the contract that if true is returned, the next call to @@ -846,9 +868,9 @@ impl Receiver { ) -> Result { // SAFETY: the existence of the `self` parameter serves as a certificate that the receiver // is still alive, meaning that even if the sender was dropped then it would have observed - // the fact that we're still alive and left the responsibility of deallocating the - // channel to us, so `self.channel` is valid - let channel = unsafe { self.channel_ptr.as_ref() }; + // the fact that we're still alive and left the responsibility of releasing the + // channel storage to us, so the channel in `self.storage` is valid + let channel = unsafe { self.storage.as_ref() }; // ORDERING: synchronize with the write of the message match channel.state.load(Acquire) { @@ -918,14 +940,16 @@ impl Receiver { _ => unreachable!(), } } +} +impl Receiver> { /// Consumes the Receiver, returning a raw pointer to the channel on the heap. /// /// This is intended to simplify using oneshot channels with some FFI code. The only safe thing /// to do with the returned pointer is to later reconstruct the Receiver with /// [Receiver::from_raw]. Memory will leak if the Receiver is never reconstructed. pub fn into_raw(self) -> *mut () { - let raw = self.channel_ptr.as_ptr() as *mut (); + let raw = self.storage.to_raw().as_ptr() as *mut (); mem::forget(self); raw } @@ -939,7 +963,8 @@ impl Receiver { /// Constructing multiple Receivers from the same raw pointer leads to undefined behavior. pub unsafe fn from_raw(raw: *mut ()) -> Self { Self { - channel_ptr: NonNull::new_unchecked(raw as *mut Channel), + storage: Global::from_raw(NonNull::new_unchecked(raw as *mut Channel)), + _invariant: PhantomData, } } } @@ -951,9 +976,9 @@ impl core::future::Future for Receiver { fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { // SAFETY: the existence of the `self` parameter serves as a certificate that the receiver // is still alive, meaning that even if the sender was dropped then it would have observed - // the fact that we're still alive and left the responsibility of deallocating the - // channel to us, so `self.channel` is valid - let channel = unsafe { self.channel_ptr.as_ref() }; + // the fact that we're still alive and left the responsibility of releasing the + // channel storage to us, so the channel in `self.storage` is valid + let channel = unsafe { self.storage.as_ref() }; // ORDERING: we use acquire ordering to synchronize with the store of the message. match channel.state.load(Acquire) { @@ -1038,11 +1063,11 @@ impl core::future::Future for Receiver { } } -impl Drop for Receiver { +impl> Drop for Receiver { fn drop(&mut self) { // SAFETY: since the receiving side is still alive the sender would have observed that and - // left deallocating the channel allocation to us. - let channel = unsafe { self.channel_ptr.as_ref() }; + // left releasing the channel storage to us. + let channel = unsafe { self.storage.as_ref() }; // Set the channel state to disconnected and read what state the receiver was in match channel.state.swap(DISCONNECTED, Acquire) { @@ -1054,7 +1079,7 @@ impl Drop for Receiver { unsafe { channel.drop_message() }; // SAFETY: see safety comment at top of function - unsafe { dealloc(self.channel_ptr) }; + unsafe { self.storage.release() }; } // The receiver has been polled. #[cfg(feature = "async")] @@ -1065,7 +1090,7 @@ impl Drop for Receiver { // The sender was already dropped. We are responsible for freeing the channel. DISCONNECTED => { // SAFETY: see safety comment at top of function - unsafe { dealloc(self.channel_ptr) }; + unsafe { self.storage.release() }; } // This receiver was previously polled, so the channel was in the RECEIVING state. // But the sender has observed the RECEIVING state and is currently reading the waker @@ -1088,7 +1113,7 @@ impl Drop for Receiver { } } // SAFETY: see safety comment at top of function - unsafe { dealloc(self.channel_ptr) }; + unsafe { self.storage.release() }; } _ => unreachable!(), } @@ -1123,7 +1148,7 @@ use states::*; /// * The message in the channel. This memory is uninitialized until the message is sent. /// * The waker instance for the thread or task that is currently receiving on this channel. /// This memory is uninitialized until the receiver starts receiving. -struct Channel { +pub(crate) struct Channel { state: AtomicU8, message: UnsafeCell>, waker: UnsafeCell>, @@ -1336,8 +1361,3 @@ fn receiver_waker_size() { #[cfg(all(feature = "std", feature = "async"))] const RECEIVER_USED_SYNC_AND_ASYNC_ERROR: &str = "Invalid to call a blocking receive method on oneshot::Receiver after it has been polled"; - -#[inline] -pub(crate) unsafe fn dealloc(channel: NonNull>) { - drop(Box::from_raw(channel.as_ptr())) -} diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000..9680b42 --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,128 @@ +use core::ptr::NonNull; + +use crate::Channel; + +#[cfg(not(oneshot_loom))] +use crate::alloc::boxed::Box; +#[cfg(oneshot_loom)] +use crate::loombox::Box; + +/// The mechanism used to manage the storage of the inner state of a channel of `T`. +#[expect(private_bounds, reason = "sealed trait with private API surface")] +pub trait Storage: StoragePrivate {} + +/// Usage model is to treat the implementing type as if it were a `NonNull>`. +/// +/// That is, it can be cloned freely and every clone (pointer) points to the same underlying data. +/// Dropping the object itself only drops the object (pointer), not the thing it points to. +/// To drop the data within and release the storage capacity, `release()` must be called explicitly. +/// +/// # Safety +/// +/// Implementations must implement the usage model described above, acting as pointers. +pub(crate) unsafe trait StoragePrivate { + /// Initializes the storage with a new `Channel`, overwriting existing contents. + /// + /// # Safety + /// + /// This must not be called more than once and must not be called after `release()`. + #[expect(dead_code, reason = "future implementations will use this")] + unsafe fn initialize(&mut self); + + /// Releases the capacity that provides this storage. + /// + /// This will drop the `Channel` and invalidate all clones of this storage. + /// + /// This must be called exactly once for each family of clones to avoid resource leaks. + /// + /// # Safety + /// + /// This must not be called multiple times on the same family of clones. + unsafe fn release(&mut self); + + /// Dereferences the stored `Channel`. + /// + /// # Safety + /// + /// The caller must guarantee that `initialize()` has been called and `release()` has not been + /// called on any of the clones of this storage. + unsafe fn as_ref(&self) -> &Channel; + + /// Clones the storage, returning a new instance that points to the same underlying data. + /// + /// This is implemented as an inherent method to avoid exposing the `Clone` trait + /// to users of implementation types outside this crate. Only the logic in this crate + /// should be cloning the storage objects. + fn clone(&self) -> Self; +} + +impl, T> Storage for S {} + +/// The storage of the inner state of a channel is allocated via the Rust global allocator. +#[derive(Debug)] +pub struct Global { + ptr: NonNull>, +} + +impl Global { + /// # Safety + /// + /// The caller must not call `initialize()` - on this implementation, `new()` implicitly + /// calls `initialize()` already, so calling it again would violate the trait safety contract. + pub(crate) unsafe fn new() -> Self { + let ptr = NonNull::from(Box::leak(Box::new(Channel::new()))); + + Global { ptr } + } + + /// Obtains the raw heap pointer that this storage object wraps. + /// + /// Using this pointer, an equivalent storage object be reconstructed with `Global::from_raw()`. + pub(crate) fn to_raw(&self) -> NonNull> { + self.ptr + } + + /// Reconstructs a storage object previously created with `to_raw()`. + /// + /// # Safety + /// + /// All the type invariants must remain in place - the recreated storage object + /// rejoins the same family of clones that it was created from. + pub(crate) unsafe fn from_raw(raw: NonNull>) -> Self { + Global { ptr: raw } + } +} + +// SAFETY: We implement the "this is just a fancy pointer" model as required by the trait. +unsafe impl StoragePrivate for Global { + unsafe fn as_ref(&self) -> &Channel { + // SAFETY: Yes, our pointer is valid and points to a `Channel`. + // The caller is responsible for ensuring that `initialize()` has been called. + unsafe { self.ptr.as_ref() } + } + + unsafe fn initialize(&mut self) { + // SAFETY: This is a valid location for a `Channel`, and we are initializing it. + // The caller is responsible for ensuring that this is not called more than once per family. + // The caller is also responsible for ensuring that `release()` has not been called on the family. + unsafe { + self.ptr.as_ptr().write(Channel::new()); + } + } + + unsafe fn release(&mut self) { + dealloc(self.ptr); + + // We rely on safety requirements to ensure this is never used again. + self.ptr = NonNull::dangling(); + } + + fn clone(&self) -> Self { + Global { ptr: self.ptr } + } +} + +#[inline] +unsafe fn dealloc(channel: NonNull>) { + drop(Box::from_raw(channel.as_ptr())) +} From 2c502c8809ba07250f573e23426e07d8004a5711 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Fri, 11 Jul 2025 04:57:00 +0300 Subject: [PATCH 2/9] Filesystem structure --- src/storage.rs | 131 ++---------------------------------- src/storage/abstractions.rs | 52 ++++++++++++++ src/storage/global.rs | 77 +++++++++++++++++++++ 3 files changed, 133 insertions(+), 127 deletions(-) create mode 100644 src/storage/abstractions.rs create mode 100644 src/storage/global.rs diff --git a/src/storage.rs b/src/storage.rs index 9680b42..6e1bee5 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,128 +1,5 @@ -use core::ptr::NonNull; +mod abstractions; +mod global; -use crate::Channel; - -#[cfg(not(oneshot_loom))] -use crate::alloc::boxed::Box; -#[cfg(oneshot_loom)] -use crate::loombox::Box; - -/// The mechanism used to manage the storage of the inner state of a channel of `T`. -#[expect(private_bounds, reason = "sealed trait with private API surface")] -pub trait Storage: StoragePrivate {} - -/// Usage model is to treat the implementing type as if it were a `NonNull>`. -/// -/// That is, it can be cloned freely and every clone (pointer) points to the same underlying data. -/// Dropping the object itself only drops the object (pointer), not the thing it points to. -/// To drop the data within and release the storage capacity, `release()` must be called explicitly. -/// -/// # Safety -/// -/// Implementations must implement the usage model described above, acting as pointers. -pub(crate) unsafe trait StoragePrivate { - /// Initializes the storage with a new `Channel`, overwriting existing contents. - /// - /// # Safety - /// - /// This must not be called more than once and must not be called after `release()`. - #[expect(dead_code, reason = "future implementations will use this")] - unsafe fn initialize(&mut self); - - /// Releases the capacity that provides this storage. - /// - /// This will drop the `Channel` and invalidate all clones of this storage. - /// - /// This must be called exactly once for each family of clones to avoid resource leaks. - /// - /// # Safety - /// - /// This must not be called multiple times on the same family of clones. - unsafe fn release(&mut self); - - /// Dereferences the stored `Channel`. - /// - /// # Safety - /// - /// The caller must guarantee that `initialize()` has been called and `release()` has not been - /// called on any of the clones of this storage. - unsafe fn as_ref(&self) -> &Channel; - - /// Clones the storage, returning a new instance that points to the same underlying data. - /// - /// This is implemented as an inherent method to avoid exposing the `Clone` trait - /// to users of implementation types outside this crate. Only the logic in this crate - /// should be cloning the storage objects. - fn clone(&self) -> Self; -} - -impl, T> Storage for S {} - -/// The storage of the inner state of a channel is allocated via the Rust global allocator. -#[derive(Debug)] -pub struct Global { - ptr: NonNull>, -} - -impl Global { - /// # Safety - /// - /// The caller must not call `initialize()` - on this implementation, `new()` implicitly - /// calls `initialize()` already, so calling it again would violate the trait safety contract. - pub(crate) unsafe fn new() -> Self { - let ptr = NonNull::from(Box::leak(Box::new(Channel::new()))); - - Global { ptr } - } - - /// Obtains the raw heap pointer that this storage object wraps. - /// - /// Using this pointer, an equivalent storage object be reconstructed with `Global::from_raw()`. - pub(crate) fn to_raw(&self) -> NonNull> { - self.ptr - } - - /// Reconstructs a storage object previously created with `to_raw()`. - /// - /// # Safety - /// - /// All the type invariants must remain in place - the recreated storage object - /// rejoins the same family of clones that it was created from. - pub(crate) unsafe fn from_raw(raw: NonNull>) -> Self { - Global { ptr: raw } - } -} - -// SAFETY: We implement the "this is just a fancy pointer" model as required by the trait. -unsafe impl StoragePrivate for Global { - unsafe fn as_ref(&self) -> &Channel { - // SAFETY: Yes, our pointer is valid and points to a `Channel`. - // The caller is responsible for ensuring that `initialize()` has been called. - unsafe { self.ptr.as_ref() } - } - - unsafe fn initialize(&mut self) { - // SAFETY: This is a valid location for a `Channel`, and we are initializing it. - // The caller is responsible for ensuring that this is not called more than once per family. - // The caller is also responsible for ensuring that `release()` has not been called on the family. - unsafe { - self.ptr.as_ptr().write(Channel::new()); - } - } - - unsafe fn release(&mut self) { - dealloc(self.ptr); - - // We rely on safety requirements to ensure this is never used again. - self.ptr = NonNull::dangling(); - } - - fn clone(&self) -> Self { - Global { ptr: self.ptr } - } -} - -#[inline] -unsafe fn dealloc(channel: NonNull>) { - drop(Box::from_raw(channel.as_ptr())) -} +pub use abstractions::*; +pub use global::*; diff --git a/src/storage/abstractions.rs b/src/storage/abstractions.rs new file mode 100644 index 0000000..5dcf1e7 --- /dev/null +++ b/src/storage/abstractions.rs @@ -0,0 +1,52 @@ +use crate::Channel; + +/// The mechanism used to manage the storage of the inner state of a channel of `T`. +#[expect(private_bounds, reason = "sealed trait with private API surface")] +pub trait Storage: StoragePrivate {} + +/// Usage model is to treat the implementing type as if it were a `NonNull>`. +/// +/// That is, it can be cloned freely and every clone (pointer) points to the same underlying data. +/// Dropping the object itself only drops the object (pointer), not the thing it points to. +/// To drop the data within and release the storage capacity, `release()` must be called explicitly. +/// +/// # Safety +/// +/// Implementations must implement the usage model described above, acting as pointers. +pub(crate) unsafe trait StoragePrivate { + /// Initializes the storage with a new `Channel`, overwriting existing contents. + /// + /// # Safety + /// + /// This must not be called more than once and must not be called after `release()`. + #[expect(dead_code, reason = "future implementations will use this")] + unsafe fn initialize(&mut self); + + /// Releases the capacity that provides this storage. + /// + /// This will drop the `Channel` and invalidate all clones of this storage. + /// + /// This must be called exactly once for each family of clones to avoid resource leaks. + /// + /// # Safety + /// + /// This must not be called multiple times on the same family of clones. + unsafe fn release(&mut self); + + /// Dereferences the stored `Channel`. + /// + /// # Safety + /// + /// The caller must guarantee that `initialize()` has been called and `release()` has not been + /// called on any of the clones of this storage. + unsafe fn as_ref(&self) -> &Channel; + + /// Clones the storage, returning a new instance that points to the same underlying data. + /// + /// This is implemented as an inherent method to avoid exposing the `Clone` trait + /// to users of implementation types outside this crate. Only the logic in this crate + /// should be cloning the storage objects. + fn clone(&self) -> Self; +} + +impl, T> Storage for S {} diff --git a/src/storage/global.rs b/src/storage/global.rs new file mode 100644 index 0000000..7f7ed8e --- /dev/null +++ b/src/storage/global.rs @@ -0,0 +1,77 @@ +use core::ptr::NonNull; + +use crate::{Channel, StoragePrivate}; + +#[cfg(not(oneshot_loom))] +use crate::alloc::boxed::Box; +#[cfg(oneshot_loom)] +use crate::loombox::Box; + +/// The storage of the inner state of a channel is allocated via the Rust global allocator. +#[derive(Debug)] +pub struct Global { + ptr: NonNull>, +} + +impl Global { + /// # Safety + /// + /// The caller must not call `initialize()` - on this implementation, `new()` implicitly + /// calls `initialize()` already, so calling it again would violate the trait safety contract. + pub(crate) unsafe fn new() -> Self { + let ptr = NonNull::from(Box::leak(Box::new(Channel::new()))); + + Global { ptr } + } + + /// Obtains the raw heap pointer that this storage object wraps. + /// + /// Using this pointer, an equivalent storage object be reconstructed with `Global::from_raw()`. + pub(crate) fn to_raw(&self) -> NonNull> { + self.ptr + } + + /// Reconstructs a storage object previously created with `to_raw()`. + /// + /// # Safety + /// + /// All the type invariants must remain in place - the recreated storage object + /// rejoins the same family of clones that it was created from. + pub(crate) unsafe fn from_raw(raw: NonNull>) -> Self { + Global { ptr: raw } + } +} + +// SAFETY: We implement the "this is just a fancy pointer" model as required by the trait. +unsafe impl StoragePrivate for Global { + unsafe fn as_ref(&self) -> &Channel { + // SAFETY: Yes, our pointer is valid and points to a `Channel`. + // The caller is responsible for ensuring that `initialize()` has been called. + unsafe { self.ptr.as_ref() } + } + + unsafe fn initialize(&mut self) { + // SAFETY: This is a valid location for a `Channel`, and we are initializing it. + // The caller is responsible for ensuring that this is not called more than once per family. + // The caller is also responsible for ensuring that `release()` has not been called on the family. + unsafe { + self.ptr.as_ptr().write(Channel::new()); + } + } + + unsafe fn release(&mut self) { + dealloc(self.ptr); + + // We rely on safety requirements to ensure this is never used again. + self.ptr = NonNull::dangling(); + } + + fn clone(&self) -> Self { + Global { ptr: self.ptr } + } +} + +#[inline] +unsafe fn dealloc(channel: NonNull>) { + drop(Box::from_raw(channel.as_ptr())) +} From cc9bba47bd06f316ee1d6035edd34455d33b8d21 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Fri, 11 Jul 2025 05:26:42 +0300 Subject: [PATCH 3/9] Implement External storage model --- src/lib.rs | 35 +++++++++++-- src/storage.rs | 2 + src/storage/abstractions.rs | 8 --- src/storage/external.rs | 99 +++++++++++++++++++++++++++++++++++++ src/storage/global.rs | 20 ++------ 5 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 src/storage/external.rs diff --git a/src/lib.rs b/src/lib.rs index edd868e..a12e1f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,9 +188,7 @@ pub use errors::*; /// Creates a new oneshot channel and returns the two endpoints, [`Sender`] and [`Receiver`]. pub fn channel() -> (Sender, Receiver) { - // SAFETY: This implicitly calls `initialize()` on the storage, so we are not allowed - // to call it again. OK, we do not. - let storage = unsafe { Global::::new() }; + let storage = Global::::new(); ( Sender { @@ -204,6 +202,37 @@ pub fn channel() -> (Sender, Receiver) { ) } +/// Creates a new oneshot channel using a custom object to store its internal state, +/// returning the two endpoints, [`Sender`] and [`Receiver`]. +/// +/// # Safety +/// +/// The caller must guarantee that the provided `ChannelStorage` pointer is valid and +/// the storage is not concurrently in use by any other `oneshot` channel. +/// +/// The caller must guarantee that no `&mut` exclusive references are created to the +/// `ChannelStorage` (including to its parent object if embedded into another type) +/// for the lifetime of the channel and any associated error types (i.e. until +/// the `release` fn is called). +pub unsafe fn channel_with_storage( + storage: NonNull>, + release: fn(NonNull>), +) -> (Sender>, Receiver>) { + // SAFETY: Forwarding the safety requirements to the caller. + let external = unsafe { External::new(storage, release) }; + + ( + Sender { + storage: external.clone(), + _invariant: PhantomData, + }, + Receiver { + storage: external, + _invariant: PhantomData, + }, + ) +} + /// Sending end of a oneshot channel. /// /// Created and returned from the [`channel`] function. diff --git a/src/storage.rs b/src/storage.rs index 6e1bee5..99bfd7c 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,5 +1,7 @@ mod abstractions; +mod external; mod global; pub use abstractions::*; +pub use external::*; pub use global::*; diff --git a/src/storage/abstractions.rs b/src/storage/abstractions.rs index 5dcf1e7..954dfb1 100644 --- a/src/storage/abstractions.rs +++ b/src/storage/abstractions.rs @@ -14,14 +14,6 @@ pub trait Storage: StoragePrivate {} /// /// Implementations must implement the usage model described above, acting as pointers. pub(crate) unsafe trait StoragePrivate { - /// Initializes the storage with a new `Channel`, overwriting existing contents. - /// - /// # Safety - /// - /// This must not be called more than once and must not be called after `release()`. - #[expect(dead_code, reason = "future implementations will use this")] - unsafe fn initialize(&mut self); - /// Releases the capacity that provides this storage. /// /// This will drop the `Channel` and invalidate all clones of this storage. diff --git a/src/storage/external.rs b/src/storage/external.rs new file mode 100644 index 0000000..e238ce0 --- /dev/null +++ b/src/storage/external.rs @@ -0,0 +1,99 @@ +use core::{cell::UnsafeCell, ptr::NonNull}; + +use crate::{Channel, StoragePrivate}; + +/// Marker type indicating that the inner state storage of a channel is provided externally +/// to the `oneshot` crate by the creator of the channel. +/// +/// This allows for advanced storage strategies such as embedding the inner state inside another +/// type and reusing the storage for the inner state across multiple consecutive channels. +/// +/// The owner of the channel must provide a pointer to a `NonNull>` and a function +/// to call when the storage is no longer needed. What the `oneshot` crate does with the storage +/// object is opaque to an external observer and is equivalent to a `&mut` exclusive borrow of the +/// storage for the duration of the channel's lifetime and any associated error types (i.e. until +/// the `release` fn is called). +pub struct External { + // Not actually a marker type but as far as the public API is concerned, + // it is a marker type because it is never created/referenced by user code. + ptr: NonNull>, + release: fn(NonNull>), +} + +impl External { + /// # Safety + /// + /// The caller must guarantee that the provided `ChannelStorage` pointer is valid and + /// the storage is not concurrently in use by any other `oneshot` channel. + /// + /// The caller must guarantee that no `&mut` exclusive references are created to the + /// `ChannelStorage` (including to its parent object if embedded into another type) + /// for the lifetime of the channel and any associated error types (i.e. until + /// the `release` fn is called). + pub(crate) unsafe fn new( + storage: NonNull>, + release: fn(NonNull>), + ) -> Self { + External { + ptr: storage, + release, + } + } +} + +/// Provides externally managed storage for a `oneshot` channel of `T`. +/// +/// This may be embedded into another type and/or reused across multiple channels to reduce +/// memory allocation pressure and improve the efficiency of using `oneshot` channels. +pub struct ChannelStorage { + // The `StoragePrivate` API contract requires the channel to explicitly call `release()` when + // this needs to be cleaned up, so unless someone calls `release()`, we will assume this is + // an inert `Channel` (either never used or already released) that does not require cleanup + // beyond being simply dropped (i.e. that its `MaybeUninit` fields are not initialized). + channel: UnsafeCell>, +} + +impl ChannelStorage { + pub fn new() -> Self { + ChannelStorage { + channel: UnsafeCell::new(Channel::new()), + } + } +} + +impl Default for ChannelStorage { + fn default() -> Self { + Self::new() + } +} + +// SAFETY: We implement the "this is just a fancy pointer" model as required by the trait. +unsafe impl StoragePrivate for External { + unsafe fn release(&mut self) { + // The caller itself manages the `Channel` within, all we need to do is inform + // the owner that their storage is no longer needed by us. + (self.release)(self.ptr); + + #[cfg(debug_assertions)] + { + // Just to make any anomalies easier to detect. + self.ptr = NonNull::dangling(); + } + } + + unsafe fn as_ref(&self) -> &Channel { + // SAFETY: `External::new()` requires a guarantee that no `&mut` exclusive references + // exist, so creating a shared reference is valid. + let storage = unsafe { self.ptr.as_ref() }; + + // SAFETY: We only ever create shared references to the `Channel`, so this is valid. + unsafe { &*storage.channel.get() } + } + + fn clone(&self) -> Self { + External { + ptr: self.ptr, + release: self.release, + } + } +} diff --git a/src/storage/global.rs b/src/storage/global.rs index 7f7ed8e..0c63a3e 100644 --- a/src/storage/global.rs +++ b/src/storage/global.rs @@ -7,18 +7,17 @@ use crate::alloc::boxed::Box; #[cfg(oneshot_loom)] use crate::loombox::Box; -/// The storage of the inner state of a channel is allocated via the Rust global allocator. +/// Marker type indicating that the inner state storage of a channel is allocated via the +/// Rust global allocator. #[derive(Debug)] pub struct Global { + // Not actually a marker type but as far as the public API is concerned, + // it is a marker type because it is never created/referenced by user code. ptr: NonNull>, } impl Global { - /// # Safety - /// - /// The caller must not call `initialize()` - on this implementation, `new()` implicitly - /// calls `initialize()` already, so calling it again would violate the trait safety contract. - pub(crate) unsafe fn new() -> Self { + pub(crate) fn new() -> Self { let ptr = NonNull::from(Box::leak(Box::new(Channel::new()))); Global { ptr } @@ -50,15 +49,6 @@ unsafe impl StoragePrivate for Global { unsafe { self.ptr.as_ref() } } - unsafe fn initialize(&mut self) { - // SAFETY: This is a valid location for a `Channel`, and we are initializing it. - // The caller is responsible for ensuring that this is not called more than once per family. - // The caller is also responsible for ensuring that `release()` has not been called on the family. - unsafe { - self.ptr.as_ptr().write(Channel::new()); - } - } - unsafe fn release(&mut self) { dealloc(self.ptr); From 1c13a2437d1d1dde49a3eb22852b20cfa3c7449b Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Fri, 11 Jul 2025 05:42:50 +0300 Subject: [PATCH 4/9] External storage - more examples and fixes --- examples/external_storage_embedded_reused.rs | 40 ++++++++++++++++++++ examples/external_storage_on_heap.rs | 20 ++++++++++ examples/external_storage_on_stack.rs | 16 ++++++++ src/storage/external.rs | 7 ++++ 4 files changed, 83 insertions(+) create mode 100644 examples/external_storage_embedded_reused.rs create mode 100644 examples/external_storage_on_heap.rs create mode 100644 examples/external_storage_on_stack.rs diff --git a/examples/external_storage_embedded_reused.rs b/examples/external_storage_embedded_reused.rs new file mode 100644 index 0000000..28aa6e6 --- /dev/null +++ b/examples/external_storage_embedded_reused.rs @@ -0,0 +1,40 @@ +use core::ptr::NonNull; + +use oneshot::ChannelStorage; + +fn main() { + struct Container { + generation: usize, + + // NB! We are not allowed to create `&mut` exclusive references to this or the parent type + // when the `ChannelStorage` is in use because that would violate the Rust aliasing rules + // as the channel essentially takes a shared reference to the storage object. + storage: ChannelStorage, + } + + let mut container = Container { + generation: 0, + storage: ChannelStorage::new(), + }; + + // SAFETY: We promise that no other channel is using this storage and that it stays alive + // at least as long as the channel itself - guaranteed because it is a local variable and + // the channel endpoints are local variables with lesser scope. + let (sender, receiver) = + unsafe { oneshot::channel_with_storage(NonNull::from(&container.storage), |_| {}) }; + + sender.send(1234).unwrap(); + assert_eq!(receiver.recv().unwrap(), 1234); + + // The storage is not in use anymore, so we can now mutate the container. + container.generation += 1; + + // SAFETY: We promise that no other channel is using this storage and that it stays alive + // at least as long as the channel itself - guaranteed because it is a local variable and + // the channel endpoints are local variables with lesser scope. + let (sender, receiver) = + unsafe { oneshot::channel_with_storage(NonNull::from(&container.storage), |_| {}) }; + + sender.send(5678).unwrap(); + assert_eq!(receiver.recv().unwrap(), 5678); +} diff --git a/examples/external_storage_on_heap.rs b/examples/external_storage_on_heap.rs new file mode 100644 index 0000000..ebc09dc --- /dev/null +++ b/examples/external_storage_on_heap.rs @@ -0,0 +1,20 @@ +use core::ptr::NonNull; + +use oneshot::ChannelStorage; + +fn main() { + // This is functionally identical to just using `oneshot::channel()`. + let storage = NonNull::from(Box::leak(Box::new(ChannelStorage::::new()))); + + // SAFETY: We promise that no other channel is using this storage and that it stays alive + // at least as long as the channel itself - guaranteed because it is a leaked box, so the + // only thing that will destroy it is the release fn we provide here. + let (sender, receiver) = unsafe { + oneshot::channel_with_storage(storage, |storage| { + drop(Box::from_raw(storage.as_ptr())); + }) + }; + + sender.send(1234).unwrap(); + assert_eq!(receiver.recv().unwrap(), 1234); +} diff --git a/examples/external_storage_on_stack.rs b/examples/external_storage_on_stack.rs new file mode 100644 index 0000000..1b9d508 --- /dev/null +++ b/examples/external_storage_on_stack.rs @@ -0,0 +1,16 @@ +use core::ptr::NonNull; + +use oneshot::ChannelStorage; + +fn main() { + let storage = ChannelStorage::::new(); + + // SAFETY: We promise that no other channel is using this storage and that it stays alive + // at least as long as the channel itself - guaranteed because it is a local variable and + // the channel endpoints are local variables with lesser scope. + let (sender, receiver) = + unsafe { oneshot::channel_with_storage(NonNull::from(&storage), |_| {}) }; + + sender.send(1234).unwrap(); + assert_eq!(receiver.recv().unwrap(), 1234); +} diff --git a/src/storage/external.rs b/src/storage/external.rs index e238ce0..f4c51d3 100644 --- a/src/storage/external.rs +++ b/src/storage/external.rs @@ -34,6 +34,13 @@ impl External { storage: NonNull>, release: fn(NonNull>), ) -> Self { + // SAFETY: The caller guarantees that no `&mut` exclusive references + // exist, so creating a shared reference is valid. + let storage_ref = unsafe { storage.as_ref() }; + + // The first thing we need to do is initialize the storage with a clean `Channel`. + storage_ref.channel.get().write(Channel::new()); + External { ptr: storage, release, From b9ec27f8b54edff7d72971512637bc11ce435a86 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Fri, 11 Jul 2025 06:08:20 +0300 Subject: [PATCH 5/9] Comments tidy --- tests/assert_mem.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/assert_mem.rs b/tests/assert_mem.rs index a993ad7..b923eb1 100644 --- a/tests/assert_mem.rs +++ b/tests/assert_mem.rs @@ -1,7 +1,8 @@ use oneshot::{Receiver, Sender}; use std::mem; -/// Just sanity check that both channel endpoints stay the size of a single pointer. +/// Just sanity check that both channel endpoints stay the size of a single pointer +/// when using the `Global` storage model, which is the default. #[test] fn channel_endpoints_single_pointer() { const PTR_SIZE: usize = mem::size_of::<*const ()>(); From 150a4d7d698e7f37e3e2faa0435330f633bde9ca Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Fri, 11 Jul 2025 06:20:14 +0300 Subject: [PATCH 6/9] Require feature="std" in new examples --- examples/external_storage_embedded_reused.rs | 6 ++++++ examples/external_storage_on_heap.rs | 6 ++++++ examples/external_storage_on_stack.rs | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/examples/external_storage_embedded_reused.rs b/examples/external_storage_embedded_reused.rs index 28aa6e6..5e6c719 100644 --- a/examples/external_storage_embedded_reused.rs +++ b/examples/external_storage_embedded_reused.rs @@ -2,6 +2,7 @@ use core::ptr::NonNull; use oneshot::ChannelStorage; +#[cfg(feature = "std")] fn main() { struct Container { generation: usize, @@ -38,3 +39,8 @@ fn main() { sender.send(5678).unwrap(); assert_eq!(receiver.recv().unwrap(), 5678); } + +#[cfg(not(feature = "std"))] +fn main() { + panic!("This example is only for when the \"std\" feature is used"); +} diff --git a/examples/external_storage_on_heap.rs b/examples/external_storage_on_heap.rs index ebc09dc..2bb51d2 100644 --- a/examples/external_storage_on_heap.rs +++ b/examples/external_storage_on_heap.rs @@ -2,6 +2,7 @@ use core::ptr::NonNull; use oneshot::ChannelStorage; +#[cfg(feature = "std")] fn main() { // This is functionally identical to just using `oneshot::channel()`. let storage = NonNull::from(Box::leak(Box::new(ChannelStorage::::new()))); @@ -18,3 +19,8 @@ fn main() { sender.send(1234).unwrap(); assert_eq!(receiver.recv().unwrap(), 1234); } + +#[cfg(not(feature = "std"))] +fn main() { + panic!("This example is only for when the \"std\" feature is used"); +} diff --git a/examples/external_storage_on_stack.rs b/examples/external_storage_on_stack.rs index 1b9d508..cca209a 100644 --- a/examples/external_storage_on_stack.rs +++ b/examples/external_storage_on_stack.rs @@ -2,6 +2,7 @@ use core::ptr::NonNull; use oneshot::ChannelStorage; +#[cfg(feature = "std")] fn main() { let storage = ChannelStorage::::new(); @@ -14,3 +15,8 @@ fn main() { sender.send(1234).unwrap(); assert_eq!(receiver.recv().unwrap(), 1234); } + +#[cfg(not(feature = "std"))] +fn main() { + panic!("This example is only for when the \"std\" feature is used"); +} From 2f9015dbc27002148734d6bb12fa3ce97f2a0691 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Fri, 11 Jul 2025 06:21:43 +0300 Subject: [PATCH 7/9] Move use statements inside main() --- examples/external_storage_embedded_reused.rs | 8 ++++---- examples/external_storage_on_heap.rs | 8 ++++---- examples/external_storage_on_stack.rs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/external_storage_embedded_reused.rs b/examples/external_storage_embedded_reused.rs index 5e6c719..3ec3360 100644 --- a/examples/external_storage_embedded_reused.rs +++ b/examples/external_storage_embedded_reused.rs @@ -1,9 +1,9 @@ -use core::ptr::NonNull; - -use oneshot::ChannelStorage; - #[cfg(feature = "std")] fn main() { + use core::ptr::NonNull; + + use oneshot::ChannelStorage; + struct Container { generation: usize, diff --git a/examples/external_storage_on_heap.rs b/examples/external_storage_on_heap.rs index 2bb51d2..24e3360 100644 --- a/examples/external_storage_on_heap.rs +++ b/examples/external_storage_on_heap.rs @@ -1,9 +1,9 @@ -use core::ptr::NonNull; - -use oneshot::ChannelStorage; - #[cfg(feature = "std")] fn main() { + use core::ptr::NonNull; + + use oneshot::ChannelStorage; + // This is functionally identical to just using `oneshot::channel()`. let storage = NonNull::from(Box::leak(Box::new(ChannelStorage::::new()))); diff --git a/examples/external_storage_on_stack.rs b/examples/external_storage_on_stack.rs index cca209a..4215974 100644 --- a/examples/external_storage_on_stack.rs +++ b/examples/external_storage_on_stack.rs @@ -1,9 +1,9 @@ -use core::ptr::NonNull; - -use oneshot::ChannelStorage; - #[cfg(feature = "std")] fn main() { + use core::ptr::NonNull; + + use oneshot::ChannelStorage; + let storage = ChannelStorage::::new(); // SAFETY: We promise that no other channel is using this storage and that it stays alive From edf9d56eba2591a3e3c963735cea162b3da2978d Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Sun, 13 Jul 2025 09:31:21 +0300 Subject: [PATCH 8/9] Update obsolete comment. --- src/storage/abstractions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/storage/abstractions.rs b/src/storage/abstractions.rs index 954dfb1..ec5979e 100644 --- a/src/storage/abstractions.rs +++ b/src/storage/abstractions.rs @@ -29,8 +29,8 @@ pub(crate) unsafe trait StoragePrivate { /// /// # Safety /// - /// The caller must guarantee that `initialize()` has been called and `release()` has not been - /// called on any of the clones of this storage. + /// The caller must guarantee that `release()` has not been called on any storage + /// object in the same family of clones. unsafe fn as_ref(&self) -> &Channel; /// Clones the storage, returning a new instance that points to the same underlying data. From 2430de001b18d7a69d833f6b423a80878607feed Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Sun, 13 Jul 2025 09:34:19 +0300 Subject: [PATCH 9/9] Try simplify lint suppression to make it work with 1.65 --- src/storage/abstractions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/abstractions.rs b/src/storage/abstractions.rs index ec5979e..b3d57ec 100644 --- a/src/storage/abstractions.rs +++ b/src/storage/abstractions.rs @@ -1,7 +1,7 @@ use crate::Channel; /// The mechanism used to manage the storage of the inner state of a channel of `T`. -#[expect(private_bounds, reason = "sealed trait with private API surface")] +#[allow(private_bounds)] // Sealed trait with private API surface. pub trait Storage: StoragePrivate {} /// Usage model is to treat the implementing type as if it were a `NonNull>`.