How can I read non-blocking from stdin?

2020-02-11 01:18发布

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 :(

标签: rust
3条回答
你好瞎i
2楼-- · 2020-02-11 01:32

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);
}
查看更多
相关推荐>>
3楼-- · 2020-02-11 01:37

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.

查看更多
够拽才男人
4楼-- · 2020-02-11 01:41

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.

查看更多
登录 后发表回答