19

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
  • 326,504
  • 69
  • 892
  • 1,159
smarr
  • 713
  • 1
  • 6
  • 17

3 Answers3

11

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
  • 121
  • 1
  • 2
9

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 PipeReader::from_fd should just work.

ArtemGr
  • 10,505
  • 2
  • 46
  • 78
  • 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
0

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
  • 144
  • 1
  • 5