std::sync::mpsc::Receiver - check for data but not take

Greetings much wiser and elder Goblin leaders,

Question: How does one simply check for the existence of available data on a std::sync::mpsc::Receiver without actually taking the value?

Given:
I have a simple mpsc channel rx and tx

let (tx, rx) = std::sync::mpsc::channel();

I know I can

  1. Block and take, using rx.recv(&self) -> Result<T, RecvError>
  2. Try to take, using rx.try_recv(&self) -> Result<T, TryRecvError>

However, I don't see an option to simply "check but not take" - both options literally take available data or error.

A peek type of option would suffice - but even just a bool that indicates there's "something there right now this very instant, but I didn't take it off the queue".

Rambling
Searching for this (here as well as google) did not prove fruitful, or anything I recognized as being an answer at least. I imagine this is such a simple and common question I probably am not searching the right keywords.

My use case, is that I need to implement an FFI callback that needs this type of behavior. I am using this channel type, to tx/rx data between the unsafe FFI callback and my main rust code - to keep all of that as separate as possible. ( It's also async - so I'm trying to communicate from sync to async - and I can, but this "check for data" is my last piece...)

I'm sure I could create a second channel for ffii only - but that seems heavy handed simply because there's no option to check for data (without actually taking it).

You can write your own wrapper to implement peeking:

use std::sync::mpsc;

struct PeekableReceiver<T> {
    rx: mpsc::Receiver<T>,
    peeked: Option<T>,
}

impl<T> PeekableReceiver<T> {
    fn peek(&mut self) -> Option<&T> {
        if self.peeked.is_some() {
            self.peeked.as_ref()
        } else {
            match self.rx.try_recv() {
                Ok(value) => {
                    self.peeked = Some(value);
                    self.peeked.as_ref()
                }
                Err(_) => None
            }
        }
    }
    
    fn try_recv(&mut self) -> Result<T, mpsc::TryRecvError> {
        if let Some(value) = self.peeked.take() {
            Ok(value)
        } else {
            self.rx.try_recv()
        }
    }
}

(Note that peeking/checking would be hazardous in a multi-consumer channel, since some other thread could take the value between the check and the actual recv. But mpsc has one receiver by design, so this is well-defined.)

2 Likes

If you used crossbeam_channel::unbounded instead of std::sync::mpsc::channel, their Receiver has an is_empty method. (Their Receiver is also cloneable, so make sure not to clone it anyways if you want to make sure the information that the channel is still non-empty remains true, in light of the possibility that other clones of the same Receiver could take away the message.)

3 Likes

Wow, thank you elders.

Technically both of these solve my problem but I can only pick one so I went with the vanilla option. and thanks for the forewarn on the race condition. Although as you say might not be the case for me here, I might venture there and it's good to know.

But crossbeam seems a perfectly fine solution as well. And same, thank you for the warning about the cloning information/warning.

Gnok