Fast Input and Output
In algorithmic problem-solving and batch CLI tools that process hundreds of thousands of lines of input and output, calling std::io::stdin().read_line(...) and println!(...) in a loop is a severe bottleneck because Stdin::read_line acquires a global mutex on every call and Stdout is line-buffered (flushing to the OS kernel on every newline \n).
Fast input and output eliminates per-line synchronization and syscall overhead by reading the entire input stream into memory once (or locking StdinLock) and wrapping StdoutLock inside a BufWriter.
Slow Default I/O (100,000 Lines = 200,000 Locks + 100,000 `write` Syscalls!):
loop { stdin().read_line(&mut buf)?; println!("{ans}"); } ---> ~450 ms (Time Limit Exceeded!)
Fast Buffered I/O (1 Read + 8 KiB Buffered Writes = ~20 Syscalls Total!):
io::read_to_string(io::stdin())? + BufWriter::new(io::stdout().lock()) ---> ~12 ms!
Comparing Standard I/O Strategies for High-Volume Input
| Strategy | Input Mechanism | Output Mechanism | Allocations | Best Used For |
|---|---|---|---|---|
| Whole-Input Slurp (Fastest & Simplest) | io::read_to_string(io::stdin())? + .split_ascii_whitespace() |
BufWriter::new(io::stdout().lock()) |
1 single String allocation for the entire input! |
Offline algorithmic problems (up to ~64 MiB input). |
Locked Streaming BufRead |
io::stdin().lock() + reused line.clear() buffer |
BufWriter::new(io::stdout().lock()) |
1 reused line buffer ($O(1)$ memory) | Interactive problems or multi-gigabyte streaming inputs. |
Default stdin() / println! |
io::stdin().read_line(&mut s) |
println!(...) |
1 per line if not cleared | Small interactive prompts ($< 1,000$ lines). |
Complete Fast I/O Token Scanner and Buffered Writer
Using std::io::read_to_string reads the entire standard input stream into a single contiguous String, after which .split_ascii_whitespace() tokenizes millions of numbers with zero additional heap allocations:
use std::io::{self, BufWriter, Write};
use std::str::{FromStr, SplitAsciiWhitespace};
pub struct FastScanner<'a> {
tokens: SplitAsciiWhitespace<'a>,
}
impl<'a> FastScanner<'a> {
pub fn new(input: &'a str) -> Self {
Self {
tokens: input.split_ascii_whitespace(),
}
}
#[inline]
pub fn next<T: FromStr>(&mut self) -> T {
let token = self.tokens.next().expect("unexpected EOF in input");
token.parse().ok().expect("failed to parse token")
}
#[inline]
pub fn next_str(&mut self) -> &'a str {
self.tokens.next().expect("unexpected EOF in input")
}
}
fn solve_queries(raw_input: &str, out: &mut impl Write) -> io::Result<()> {
let mut scan = FastScanner::new(raw_input);
let q: usize = scan.next();
for _ in 0..q {
let a: i64 = scan.next();
let b: i64 = scan.next();
writeln!(out, "{}", a + b)?;
}
Ok(())
}
fn main() -> io::Result<()> {
// In a competitive programming submission:
// let raw_input = io::read_to_string(io::stdin())?;
let raw_input = "3\n10 20\n-5 15\n1000000000 2000000000\n";
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
solve_queries(raw_input, &mut out)?;
out.flush()?;
Ok(())
}
30
10
3000000000
Streaming Interactive Problems (flush Requirement)
In interactive algorithmic problems (where your program prints a query and waits for the judge process to reply before continuing), wrapping stdout in BufWriter will hold your query in user-space memory until the 8 KiB buffer fills up—causing a deadlock unless you explicitly call out.flush()? after every query!