Is there a Rust way to check if the STDIN buffer is empty before attempting to read it?

Viewed 1544

I want my application to be able to read the input from a redirected file stream through source > my_app, but I do not want it to ask for user input in case there is no redirection.

C provides such methods (Checking the stdin buffer if it's empty) but I could not find any solution for Rust.

3 Answers

Like other users suggested, atty is a good way to check for this but it seems that there are some cases you should know as mentioned here.

I will leave this sample code in case anybody needs it and/or for future references for myself:

extern crate atty;

use std::io::{self, BufRead};

fn main() {
    if atty::is(atty::Stream::Stdin) {
        println!("nothing to do here!");
        return;
    }

    io::stdin()
        .lock()
        .lines()
        .for_each(|x| println!("{}", x.expect("error reading line")));
}
[dependencies]
atty="0.2.*"

As trentcl suggested, the way to do it is atty crate.

There are two separate questions here.

  1. "I do not want it to ask for user input in case there is no redirection." - as stated by @trentcl, the best answer is atty
  2. "Is there a way to check if the stdin buffer is empty?"

This answer is for the second question. I also wrote it as a self-contained fiddle with test cases: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a265b9c4e76d6a9e53d8cc3e5334e42c.

The Rust standard library doesn't provide a way. I've gone to the underlying OS file descriptor so I can switch it between blocking and non-blocking mode. So: start with an FD, and we'll construct a normal BufReader around it:

struct NonblockingBufReader {
  buffered: BufReader<RawFd2>,
}

struct RawFd2 {
  fd: RawFd,
}

impl NonblockingBufReader {
  /// Takes ownership of the underlying FD
  fn new<R: IntoRawFd>(underlying: R) -> NonblockingBufReader {
    let buffered = BufReader::new(RawFd2 {
      fd: underlying.into_raw_fd(),
    });
    return NonblockingBufReader { buffered };
  }
}

// Here's a private implementation of 'Read' for raw file descriptors,
// for use in BufReader...
impl std::io::Read for RawFd2 {
  fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
    assert!(buf.len() <= isize::max_value() as usize);
    match unsafe { libc::read(self.fd, buf.as_mut_ptr() as _, buf.len()) } {
      x if x < 0 => Err(std::io::Error::last_os_error()),
      x => Ok(x as usize),
    }
  }
}

But because we know our BufReader wraps an FD (rather than just some arbitrary Reader) we can get to that FD to switch it to non-blocking: (this method goes inside impl NonblockingBufReader)

/// Does BufReader::read_line but only if there's already at
/// least one byte available on the FD. In case of EOF, returns
/// an empty string.
/// Possible outcomes: (0) no-data-yet, (1) data, (2) EOF, (3) Error
fn read_line_only_if_data(&mut self) -> std::io::Result<Option<String>> 
{
  let r = unsafe {
    // The reason this is safe is we know 'inner' wraps a valid FD,
    // and we're not doing any reads on it such as would disturb BufReader.
    let fd = self.buffered.get_ref().fd;
    let flags = libc::fcntl(fd, libc::F_GETFL);
    libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
    let r = self.buffered.fill_buf();
    libc::fcntl(fd, libc::F_SETFL, flags);
    r
  };
  // Behavior of fill_buf is "Returns the contents of the internal buffer,
  // filling it with more data from the inner reader if it is empty."
  // If there were no bytes available, then (1) the internal buffer is
  // empty, (2) it'll call inner.read(), (3) that call will error WouldBlock.
  match r {
    Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None), // (0) no-data-yet
    Ok(buf) if !buf.is_empty() => {
      let mut line = String::new();
      self.buffered.read_line(&mut line)?;
      Ok(Some(line)) // (1) data, or further error
    },
    Ok(_) => Ok(Some(String::new())), // (2) EOF
    Err(e) => Err(e), // (3) Error
  }
}

For completeness, here's the normal read_line:

/// Wraps BufReader::read_line.
/// Possible outcomes: (1) data, (2) EOF, (3) Error
fn read_line(&mut self) -> std::io::Result<String> {
  let mut line = String::new();
  match self.buffered.read_line(&mut line) {
    Ok(_) => Ok(line), // EOF/data
    Err(e) => Err(e), // Error
  }
}
Related