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