Standard Output
Standard output (stdout, file descriptor 1 on Unix and STD_OUTPUT_HANDLE on Windows) is the primary output stream used by a process to emit normal program results, structured data (JSON, CSV, TSV), and user-facing text. Every call to print! and println! writes to std::io::stdout().
Buffering and Synchronization of Stdout
Like Stdin, std::io::Stdout is synchronized across threads by a global re-entrant lock and wrapped in a LineWriter by default:
- Line-buffered: Writing a newline byte (
\n, asprintln!always does) automatically flushes the internal buffer to the operating system. - Partial lines (
print!): Callingprint!("Enter name: ")without a trailing\nkeeps the bytes inside theLineWriterbuffer until either a\nis written, the buffer fills up, orio::stdout().flush()is called explicitly!
`print!` / `println!` -> `io::stdout()` -> Global `ReentrantLock<LineWriter<RawStdout>>`
|
+-------------------------------------------+-------------------------------------------+
| Contains `\n` (`println!`) | No `\n` (`print!`)
v v
Flushes immediately to OS fd 1 Stays in RAM buffer until
`io::stdout().flush()` is called!
Explicit Locking (stdout().lock()) for High-Throughput Output
Every standalone println!(...) call acquires and releases the global stdout lock. When printing thousands of lines in a loop, calling io::stdout().lock() (and wrapping it in a BufWriter) acquires the lock once and batches writes into 8 KiB chunks instead of flushing on every \n:
use std::io::{self, BufWriter, Write};
fn main() -> io::Result<()> {
let stdout = io::stdout();
// Lock `stdout` once and wrap `StdoutLock` in a `BufWriter` to avoid
// per-line lock traffic and per-line `\n` syscalls:
let mut handle = BufWriter::new(stdout.lock());
for id in 1..=3 {
writeln!(handle, "record_id={id} status=ok")?;
}
// Flush the `BufWriter` before dropping:
handle.flush()?;
Ok(())
}
record_id=1 status=ok
record_id=2 status=ok
record_id=3 status=ok
Broken Pipe Handling (writeln! vs. println!)
When a Rust binary's standard output is piped into a command that exits early (such as my_app | head -n 5), the OS closes the pipe and subsequent writes to stdout return Err(io::ErrorKind::BrokenPipe) (EPIPE):
println!(...)panics if writing tostdoutfails!writeln!(io::stdout(), ...)returnsio::Result<()>so CLI tools can catchErrorKind::BrokenPipeand exit cleanly with code0instead of printing a panic message.