Update/Edit
Just found this after posting...
TL;DR
Using Stdin
to read input from standard in, how to do I program a check, so that if a user has provided no input to standard in, the program exits? Without it, the program just hangs while Stdin
's read methods block on waiting for the end of the non-existent input.
Background
I have written a program that imports data for testing and demo purposes, optionally importing from a file when provided a PATH argument from CLI or from standard input.
Issue
However, if a user runs the program with the standard input arguments but provides nothing from standard input, my program hangs when it reads from Stdin.
Code
info!("Reading import data from stdin");
let mut buffer = Vec::new();
let mut stdin = io::stdin();
// Program hangs here if there is no input from standard input
stdin.read_to_end(&mut buffer)?;
if buffer.is_empty() {
panic!("No input provided from stdin");
}
buffer
Failed Attempts
I started out using read_to_string
(I'm reading a single JSON array as input), which seemed obviously wrong. But read_to_end
also produces the same behavior.
I've tried elaborate solutions such as
use std::io::{self, Read};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
struct ReadFromStdin<'a> {
buffer: &'a mut String,
stdin: &'a mut io::Stdin,
}
impl<'a> Future for ReadFromStdin<'a> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut buffer = String::new();
match self.stdin.read_to_string(&mut buffer) {
Ok(bytes) => {
self.buffer.push_str(&buffer);
Poll::Ready(Ok(bytes))
},
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
cx.waker().wake_by_ref();
Poll::Pending
},
Err(e) => Poll::Ready(Err(e)),
}
}
}
async fn read_from_stdin_timeout(buffer: &mut String, stdin: &mut io::Stdin, timeout: Duration) -> io::Result<usize> {
tokio::time::timeout(timeout, ReadFromStdin { buffer, stdin }).await?
}
let mut buffer = String::new();
let mut stdin = io::stdin();
// Set a timeout of 5 seconds on stdin read
let bytes = read_from_stdin_timeout(&mut buffer, &mut stdin, Duration::from_secs(5)).await?;
// Handle the result of the read operation
if bytes > 0 {
info!("Loaded {} bytes of import data from stdin", bytes);
buffer
} else {
panic!("No input from stdin");
}
And while that compiles (trying with both read_to_end
and read_to_string
) it produces the same behavior while seeming way too complicated for my purposes.