Standard Input and Output

Standard Input

Standard input (stdin, file descriptor 0 on Unix and STD_INPUT_HANDLE on Windows) is the default byte stream from which a process reads interactive keyboard input, piped output from an upstream command (cmd1 | my_app), or redirected files (my_app < input.txt).

Architecture of Process Standard Input in Rust

To prevent multiple threads from simultaneously reading and corrupting the shared input buffer, Rust's std::io::Stdin handle wraps a process-global, mutex-protected BufReader (8 KiB internal buffer):

OS File Descriptor 0 (Keyboard / Pipe / File Redirect)
       |
       v
+---------------------------------------------------+
| Global `Mutex<BufReader<RawStdin>>` (8 KiB Buffer)|
+---------------------------------------------------+
       ^
       |  `std::io::stdin()` returns a lightweight handle `Stdin`
       |  - Calling `stdin().read_line(&mut buf)` locks & unlocks per line
       |  - Calling `stdin().lock()` returns `StdinLock<'static>` (holds lock across reads!)
Access Method Locks Global Stdin Mutex? Implements Read? Implements BufRead?
io::stdin() (Stdin) Locks and unlocks on every individual call Yes No (provides convenience .read_line() and .lines())
io::stdin().lock() (StdinLock<'static>) Locks once until StdinLock is dropped Yes Yes (unlocks zero-overhead .fill_buf(), .split(), .read_until())

Detecting Interactive Terminal vs. Piped Input (IsTerminal)

Stabilized in Rust 1.70, the std::io::IsTerminal trait (implemented for Stdin, Stdout, Stderr, and File) provides .is_terminal() -> bool (replacing the external atty crate). A CLI tool can inspect io::stdin().is_terminal() to decide whether to display an interactive prompt or read piped data silently:

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

fn main() {
    let stdin = io::stdin();
    println!("stdin is_terminal (TTY): {}", stdin.is_terminal());
}
stdin is_terminal (TTY): false

Reading All of Standard Input Until EOF (io::read_to_string)

When a command-line filter processes an entire piped document in memory, std::io::read_to_string(io::stdin()) (or io::stdin().read_to_end(&mut bytes) for binary input) reads until End-of-File (EOF — Ctrl+D on Unix, Ctrl+Z on Windows, or pipe close):

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

fn count_words_from_reader(reader: impl Read) -> io::Result<usize> {
    let text = io::read_to_string(reader)?;
    Ok(text.split_whitespace().count())
}

fn main() -> io::Result<()> {
    // Using a simulated `&[u8]` slice reader (same `Read` interface as `io::stdin()`):
    let simulated_stdin: &[u8] = b"rust standard input stream\nsecond line\n";
    let words = count_words_from_reader(simulated_stdin)?;
    println!("total words read until EOF: {}", words);
    Ok(())
}
total words read until EOF: 6