Hone logo
Hone
Problems

Implementing a Oneshot Channel in Rust

A oneshot channel is a communication primitive where a sender can send a single value, and a receiver can wait to receive that value. Once the value is sent, the channel is considered "closed" and cannot be used again. This is useful for scenarios where you need to signal the completion of a task or return a result from an asynchronous operation exactly once.

Problem Description

Your task is to implement a oneshot channel in Rust. This channel should allow a single sender and a single receiver to communicate.

Key Requirements:

  1. Sender: A Sender<T> struct that holds a reference to the channel's internal state. It should have a send(value: T) method.
  2. Receiver: A Receiver<T> struct that holds a reference to the channel's internal state. It should have a recv() method.
  3. Single Send/Receive: The channel must ensure that a value can only be sent once. Subsequent send calls should ideally panic or have no effect (though panicking is preferred for clarity in this exercise). Similarly, a recv call should only yield the value once.
  4. Blocking Receive: The recv() method should block the current thread until a value is sent.
  5. Error Handling: If recv() is called after the sender has dropped without sending a value, it should return an error. If send() is called after a value has already been sent, it should panic.

Expected Behavior:

  • When send(value) is called, the value is stored internally.
  • When recv() is called and a value has been sent, it returns Ok(value).
  • When recv() is called and the sender has been dropped without sending a value, it returns Err.
  • If send() is called a second time, the program should panic.
  • The Sender and Receiver should be Send and Sync if T is Send and Sync, enabling their use across threads.

Edge Cases:

  • Dropping the Sender before sending a value.
  • Dropping the Receiver before receiving a value.
  • Calling recv() multiple times on the same Receiver.
  • Calling send() multiple times on the same Sender.

Examples

Example 1:

// Assume we have a `OneshotChannel::new()` function that returns (Sender<i32>, Receiver<i32>)
let (sender, receiver) = OneshotChannel::new();

// In a separate thread (for demonstration of blocking):
std::thread::spawn(move || {
    std::thread::sleep(std::time::Duration::from_millis(100));
    sender.send(123);
});

// In the main thread:
let received_value = receiver.recv();

// Expected Output:
// Ok(123)

Explanation: The sender sends the value 123 after a short delay. The receiver blocks until the value is available and then returns it wrapped in Ok.

Example 2:

let (sender, receiver) = OneshotChannel::new::<String>();

// Sender is dropped without sending
drop(sender);

// Receiver tries to receive
let result = receiver.recv();

// Expected Output:
// Err(ChannelClosedError)

Explanation: The sender is dropped before sending any data. When the receiver attempts to recv, it encounters the Err indicating the channel was closed without a value.

Example 3:

let (sender, receiver) = OneshotChannel::new();

sender.send(42);

// Trying to send again should panic
// sender.send(100); // This line would cause a panic

let received_value = receiver.recv();

// Expected Output (if the second send was commented out):
// Ok(42)

Explanation: The first send succeeds. If a second send were attempted, it would panic. The recv successfully retrieves the initially sent value.

Constraints

  • The channel should be able to hold any type T that implements Send.
  • The implementation should be thread-safe, allowing Sender and Receiver to be moved to different threads.
  • Performance is a consideration; avoid unnecessary copying or complex synchronization primitives if simpler ones suffice.
  • The channel should not require any external crates beyond Rust's standard library.

Notes

  • You will need to define your own error type for when recv() is called on a closed channel.
  • Consider using std::sync::Mutex or std::sync::atomic operations for managing the shared state of the channel.
  • The Arc (Atomic Reference Counter) will likely be crucial for sharing ownership of the channel's internal state between the sender and receiver, and potentially across threads.
  • Think about how to signal the receiver that a value has arrived without it constantly polling. A condition variable might be useful here.
  • When the Sender or Receiver is dropped, its destructor should correctly clean up any internal state or signal the other end if necessary.
  • The Sender and Receiver should be distinct types, enforcing the oneshot nature at the type level.
Loading editor...
rust