[Rust] Reading Input and Writing Output
When solving algorithm problems in Rust, I/O is usually the first wall you hit. It's not as straightforward as cin/cout in C++ or input()/print() in Python, but once you learn it, you can use the same pattern everywhere.
Basic Output: println!
fn main() {
println!("Hello World!");
}
The simplest way to print. Because stdout is flushed line by line and println! takes the lock on every call, it gets slow when you have a lot of output.
Fast Output: BufWriter
use std::io::{self, Write, BufWriter};
fn main() {
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
writeln!(out, "Hello World!").unwrap();
}
Wrapping stdout in a BufWriter buffers the output and writes it out in larger chunks. For problems with heavy output, the difference is large. Whatever is left in the buffer is flushed when out is dropped (usually at the end of main).
stdout.lock()→ acquires the stdout lock only onceBufWriter→ reduces the number of system calls via bufferingwriteln!→ the macro to use instead ofprintln!
Basic Input: read_line
use std::io;
fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let n: i32 = input.trim().parse().unwrap();
println!("{}", n);
}
Fine for reading a single line, but tedious when you need to read multiple lines.
Fast Input: BufReader + read_to_string
use std::io::{self, BufReader, Read};
fn main() {
let mut input = String::new();
let mut reader = BufReader::new(io::stdin().lock());
reader.read_to_string(&mut input).unwrap();
let mut iter = input.split_whitespace();
let n: i32 = iter.next().unwrap().parse().unwrap();
}
read_to_string reads the entire input at once, up to EOF. When running directly in a terminal, you'll need to press Ctrl+Z then Enter (Windows) or Ctrl+D (Linux/Mac) after entering your input to signal EOF. On online judges like BOJ, EOF is sent automatically, so you don't need to worry about it.
Note that stdin already has an internal buffer, so io::stdin().read_to_string(&mut input) works without BufReader.
split_whitespace() treats spaces, tabs, and newlines all as delimiters, making it easy to parse token by token.
Reading Two Numbers and Adding Them (BOJ 1000)
use std::io::{self, BufReader, BufWriter, Read, Write};
fn main() {
let mut input = String::new();
let mut reader = BufReader::new(io::stdin().lock());
reader.read_to_string(&mut input).unwrap();
let mut iter = input.split_whitespace();
let a: i32 = iter.next().unwrap().parse().unwrap();
let b: i32 = iter.next().unwrap().parse().unwrap();
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
writeln!(out, "{}", a + b).unwrap();
}
What Is unwrap()?
writeln! and parse() return a Result type.
- Success (
Ok(value)) →unwrap()extracts the value - Failure (
Err(error)) →unwrap()panics and terminates the program
In algorithm problems, the input format is fixed, so using unwrap() to keep things simple is fine. In production code, it's better to propagate errors with the ? operator.
What Are mut and &mut?
mut in variable declarations
In Rust, variables are immutable by default. You need mut to make them changeable.
let input = String::new(); // immutable → cannot be modified
let mut input = String::new(); // mutable → can be modified
Since read_to_string needs to write data into input, mut is required.
&mut when passing to functions
& is a reference — it borrows a value without copying it.
&input→ read-only reference (immutable reference)&mut input→ read + write reference (mutable reference)
reader.read_to_string(&mut input).unwrap();
// ^^^^^^^^^^
// We pass &mut because read_to_string needs to write into input
Since read_to_string needs to fill input with the data it reads, it requires a mutable reference via &mut.
Why Call lock()?
stdout() and stdin() can be accessed from multiple threads simultaneously, so by default they acquire and release a lock on every call.
// println! works roughly like this internally
stdout().lock() → write → unlock
stdout().lock() → write → unlock // slower the more times it runs
Calling .lock() directly lets you hold the lock once and keep using it.
let stdout = io::stdout();
let locked = stdout.lock(); // lock acquired once
let mut out = BufWriter::new(locked); // reused from here on
The same applies to stdin. Calling stdin().lock() once means no repeated lock/unlock cycles while reading, which is faster.
What Is next()?
split_whitespace() returns an iterator — an object that lets you pull values out one at a time, in order.
Each call to .next() returns the next value.
let input = "1 2 3";
let mut iter = input.split_whitespace();
iter.next() // → Some("1") first value
iter.next() // → Some("2") second value
iter.next() // → Some("3") third value
iter.next() // → None nothing left
Some and None are the Option type — they represent a value that may or may not exist. unwrap() extracts the value inside Some.
iter.next() // Some("1")
iter.next().unwrap() // "1" ← the value unwrapped from Some
What Is parse()?
parse() converts a string into a desired type. It's the equivalent of int() in Python or stoi() in C++.
let s = "42";
let n: i32 = s.parse().unwrap(); // "42" → 42 (i32)
The target type is inferred from the variable's type annotation.
let a: i32 = "42".parse().unwrap(); // 32-bit integer
let b: i64 = "42".parse().unwrap(); // 64-bit integer
let c: f64 = "3.14".parse().unwrap(); // 64-bit float
let d: usize = "42".parse().unwrap(); // unsigned integer (for array indices, etc.)
If the conversion fails, it returns Err, and unwrap() will panic.
let n: i32 = "abc".parse().unwrap(); // panic! not a number
Putting It All Together
Here's a step-by-step breakdown of parsing the input "1 2" into two integers:
let mut iter = input.split_whitespace();
// iter: an iterator that yields "1" and "2" in order
let a: i32 = iter.next().unwrap().parse().unwrap();
// ^^^^^^^^^^^ → Some("1")
// ^^^^^^^^^ → "1"
// ^^^^^^^ → Result<i32, _>
// ^^^^^^^^^ → 1 (i32)
Pull out a string with next() → unwrap Some with unwrap() → convert to a number with parse() → unwrap Result with unwrap().
Summary
| Slow | Fast | |
|---|---|---|
| Input | stdin().read_line() | BufReader + read_to_string |
| Output | println! | BufWriter + writeln! |
For competitive programming, just always use the BufReader + BufWriter combination.