Rust for Algorithmic Problem-Solving

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!