23

Is there a way to check whether data is available on stdin in Rust, or to do a read that returns immediately with the currently available data?

My goal is to be able to read the input produced for instance by cursor keys in a shell that is setup to return all read data immediately. For instance with an equivalent to: stty -echo -echok -icanon min 1 time 0.

I suppose one solution would be to use ncurses or similar libraries, but I would like to avoid any kind of large dependencies.

So far, I got only blocking input, which is not what I want:

let mut reader = stdin();
let mut s = String::new();

match reader.read_to_string(&mut s) {...} // this blocks :(
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
smarr
  • 763
  • 1
  • 6
  • 17

4 Answers4

14

Converting OP's comment into an answer:

You can spawn a thread and send data over a channel. You can then poll that channel in the main thread using try_recv.

use std::io;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::TryRecvError;
use std::{thread, time};

fn main() {
    let stdin_channel = spawn_stdin_channel();
    loop {
        match stdin_channel.try_recv() {
            Ok(key) => println!("Received: {}", key),
            Err(TryRecvError::Empty) => println!("Channel empty"),
            Err(TryRecvError::Disconnected) => panic!("Channel disconnected"),
        }
        sleep(1000);
    }
}

fn spawn_stdin_channel() -> Receiver<String> {
    let (tx, rx) = mpsc::channel::<String>();
    thread::spawn(move || loop {
        let mut buffer = String::new();
        io::stdin().read_line(&mut buffer).unwrap();
        tx.send(buffer).unwrap();
    });
    rx
}

fn sleep(millis: u64) {
    let duration = time::Duration::from_millis(millis);
    thread::sleep(duration);
}
masonforest
  • 151
  • 1
  • 2
10

Most operating systems default to work with the standard input and output in a blocking way. No wonder then that the Rust library follows in stead.

To read from a blocking stream in a non-blocking way you might create a separate thread, so that the extra thread blocks instead of the main one. Checking whether a blocking file descriptor produced some input is similar: spawn a thread, make it read the data, check whether it produced any data so far.

Here's a piece of code that I use with a similar goal of processing a pipe output interactively and that can hopefully serve as an example. It sends the data over a channel, which supports the try_recv method - allowing you to check whether the data is available or not.

Someone has told me that mio might be used to read from a pipe in a non-blocking way, so you might want to check it out too. I suspect that passing the stdin file descriptor (0) to Receiver::from_raw_fd should just work.

ArtemGr
  • 11,684
  • 3
  • 52
  • 85
  • pub fn init() -> Receiver { let (tx, rx) = channel::(); thread::spawn(move|| { while true { tx.send(read_stdin()); } }); rx } pub fn get(rx: &Receiver, old: ControlKeys) -> ControlKeys { match rx.try_recv() { Ok(key) => key, Err(_) => old } } fn read_stdin() -> ControlKeys { let mut reader = stdin(); let mut buf = &mut [0u8; 10]; match reader.read(buf) { Err(why) => panic!(...), Ok(size) => { // read buf return res; } } } – smarr May 07 '15 at 21:32
  • 1
    Threads or async/await and channels might be the right solution, perhaps most of the time. If just wanting to do O_NONBLOCK read()s without having to interface directly with libc, I found [nonblock](https://lib.rs/crates/nonblock) to be a useful and good enough crate. – sampi May 04 '22 at 19:30
  • How to asking the thread about received data while it's blocking waiting for potentionally more data? – Günter Zöchbauer Jan 30 '23 at 10:33
  • 1
    @GünterZöchbauer a thread might forward the data to the channel, which would have the non-blocking `try_recv` method – ArtemGr Jan 31 '23 at 09:28
-1

You could also potentially look at using ncurses (also on crates.io) which would allow you read in raw mode. There are a few examples in the Github repository which show how to do this.

kbknapp
  • 172
  • 1
  • 5
  • none of its examples is using non-blocking stdin read – alexzander Mar 30 '22 at 15:00
  • The OP explicitly said they want to avoid a big dependency. I still believe there is value in an answer like this, but I downvoted because you add nothing to the OP. If you would show an example of how to do that this post would be worthy. – Chayim Friedman May 04 '23 at 17:03
-1

I had the same question and got an answer that worked for me here, which I will copy below for convenience. As was pointed out to me, "tokio already comes with impl AsyncRead for Stdin, so you should be able to do something like":

async fn read_from_stdin_timeout(timeout: Duration) -> io::Result<String> {
    let mut buf = String::new();
    tokio::time::timeout(timeout, tokio::io::stdin().read_to_string(&mut buf)).await??;
    Ok(buf)
}
  • 3
    Quoting [`tokio`'s docs](https://docs.rs/tokio/latest/tokio/io/fn.stdin.html), "This handle is best used for non-interactive uses, such as when a file is piped into the application. For technical reasons, `stdin` is implemented by using an ordinary blocking read on a separate thread, and it is impossible to cancel that read. This can make shutdown of the runtime hang until the user presses enter.". – Chayim Friedman May 04 '23 at 17:00