rust-lang / rust-lang/rust

Tracking Issue for `oneshot_channel`

Open
#143,674 15 comments 6 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-tracking-issue S-tracking-unimplemented T-libs
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Feature gate: #![feature(oneshot_channel)]

This is a tracking issue for adding a one-shot channel to the standard library, as discussed in https://github.com/rust-lang/libs-team/issues/610

Public API

Note that this API is similar to oneshot, but is slightly different. The oneshot crate has methods that take &self instead of self for fallable methods, which means that it is possible for a calller to try_recv more than once, even if the value has already been sent.

By taking ownership of self in every method, we guarantee that we will never have previously received a value over the channel.

This does come at the cost of ergonomics, as we need to return the corresponding end of the channel back to the caller if the method fails.

#![feature(oneshot_channel)]

pub fn channel<T>() -> (Sender<T>, Receiver<T>) { ... }

/**************************************************************************************************/

/// Note that this API is essentially identical to the `oneshot` crate.
pub struct Sender<T> { ... }

impl<T> Sender<T> {
    /// Sends the value to the receiving end. This method should be wait-free.
    /// Can only fail if the corresponding `Receiver<T>` has been dropped.
    pub fn send(self, t: T) -> Result<(), SendError<T>> { ... }
}

/// Gives the sent value back to the caller.
/// Should this inherit from the `mpsc` module like `mpmc`?
pub struct SendError<T> { ... }

/**************************************************************************************************/

pub struct Receiver<T> { ... }

impl<T> Receiver<T> {
    /// Receives the value from the sending end, blocking the calling thread until it gets it.
    /// Can only fail if the corresponding `Sender<T>` has been dropped.
    pub fn recv(self) -> Result<T, RecvError> { ... }

    // Fallible methods.

    /// Returns if the sender has sent the value over the channel.
    /// Can only fail with `RecvError` if the corresponding `Sender<T>` has been dropped.
    /// If this method returns `Ok(true)`, then any of the `recv` methods below are
    /// guaranteed to return the value successfully without blocking.
    pub fn is_ready(&self) -> Result<bool, RecvError> { ... }

    /// Attempts to receive the sent value without blocking.
    pub fn try_recv(self) -> Result<T, TryRecvError<T>> { ... }

    /// Attempts to receive the value, returning an error if it waits longer than `timeout`.
    pub fn recv_timeout(self, timeout: Duration) -> Result<T, RecvTimeoutError<T>> { ... }

    /// Attempts to receive the value, returning an error if `deadline` is reached.
    pub fn recv_deadline(self, deadline: Instant) -> Result<T, RecvTimeoutError<T> { ... }
}

/// Should this inherit from `RecvError` in `mpsc`?
pub struct RecvError;

pub enum TryRecvError<T> {
    Empty(Receiver<T>),
    Disconnected,
}

pub enum RecvTimeoutError<T> {
    Timeout(Receiver<T>),
    Disconnected,
}
Steps / History
Unresolved Questions

Right now, mpmc's SendError<T> is simply mpsc's SendError<T> (docs here). I'm guessing that this channel should also inherit the same SendError<T> type from mpsc?. And a similar question for RecvError.

If we decide to always take ownership, what should happen on error for the recv methods? Or in other words, how should users take back ownership of the Receiver after a method has failed? Should it be encapsulated in a TryRecvError<T> or RecvTimeoutError<T> and extracted via an into_receiver method, or returned as a tuple of (Receiver<T>, TryRecvError/RecvTimeoutError)?

Edit: Resolved. Returning a tuple of (Receiver<T>, TryRecvError) is a bad idea since if the error kind is disconnected, there is zero reason we should also return the receiver back to the caller.

//! Option 1

pub fn try_recv(self) -> Result<T, TryRecvError> { ... }

pub enum TryRecvError<T> {
    Empty(Receiver<T>),
    Disconnected,
}

impl<T> TryRecvError<T> {
    pub fn into_inner(self) -> Option<Receiver<T>> {
        match self {
            Self::Empty(receiver) => Some(receiver),
            Self::Disconnected => None,
        }
    }
}

/**************************************************************************************************/

//! Option 2

/// NOT GOOD: If `TryRecvError` is the variant `Disconnected`, then returning the `Receiver`
/// doesn't make sense.
pub fn try_recv(self) -> Result<T, (Receiver<T>, TryRecvError)> { ... }

pub enum TryRecvError {
    Empty,
    Disconnected,
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the proposed public API and the Initial Prototype PR (#143741), then inspect the is_ready PR (#152557) and the unresolved error-type questions. Done means completing the remaining implementation and async API work, followed by the final comment period and stabilization PR.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
distributed-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.