Skip to content

Conversation

@sandersaares
Copy link
Contributor

Enables user-provided storage for the internal Channel<T> state, so it can be managed in an allocation-free manner (pooled, placed on stack, embedded in another object, etc).

Initial draft to facilitate design discussion - it seems to work based on initial exploration but needs more polishing and especially better test coverage.

Closes #66

Example

// This is functionally identical to just using `oneshot::channel()`.
let storage = NonNull::from(Box::leak(Box::new(ChannelStorage::<usize>::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);

Design

An important consideration was to not make the default oneshot::channel() slower/bigger. This is facilitated by generics: Sender<T> becomes Sender<T, S = Global<T>> (as well as Receiver and SendError - anything that uses Channel<T>). In this form, behavior is identical to the original.

The type parameter has a default value of Global<T> which means existing code using oneshot remains unchanged and can continue to pretend there is only 1 generic type parameter.

There is a new function that returns Sender<T, S = External<T>> which enables the new functionality:

/// 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<T>` 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<T>` (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<T>(
    storage: NonNull<ChannelStorage<T>>,
    release: fn(NonNull<ChannelStorage<T>>),
) -> (Sender<T, External<T>>, Receiver<T, External<T>>) {
    ...
}

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")]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I observe this attribute is failing one of the automated builds that uses Rust 1.65 toolchain, as the lint and the way I suppressed it are all from a too-new Rust version. What is the correctly pattern to apply here to suppress this linter warning in context of this repo's practices? I am not very familiar with how to best build using both old and new toolchains at the same time (except in dependency scenarios where lints are suppressed anyway).

Copy link
Contributor Author

@sandersaares sandersaares Jul 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, relaxing to an allow() got rid of some of the warnings but I see that 1.65 does not support this sealed trait pattern in the first place. Will leave the question of what to do with this open for now, pending design discussion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option to avoid the sealing being an issue on older Rust is to just open up the pattern to non-sealed, so anyone can make an impl Storage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support externally provided storage for Channel

1 participant