Back to Blog

[Rust] Reading Input and Writing Output

(edited: March 10, 2026)

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!

Rust
fn main() {
    println!("Hello World!");
}

The simplest way to print. Because it locks and flushes stdout on every call, it gets slow when you have a lot of output.

Fast Output: BufWriter

Rust
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 everything and flushes it all at once. For problems with heavy output, this can be tens of times faster.

  • stdout.lock() → acquires the stdout lock only once
  • BufWriter → reduces the number of system calls via buffering
  • writeln! → the macro to use instead of println!

Basic Input: read_line

Rust
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

Rust
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 (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.

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)

Rust
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()?

Functions like 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, I/O almost never fails, 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.

Rust
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)
Rust
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.

Rust
// 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.

Rust
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.

Rust
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.

Rust
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++.

Rust
let s = "42";
let n: i32 = s.parse().unwrap();  // "42" → 42 (i32)

The target type is inferred from the variable's type annotation.

Rust
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.

Rust
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:

Rust
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

SlowFast
Inputstdin().read_line()BufReader + read_to_string
Outputprintln!BufWriter + writeln!

For competitive programming, just always use the BufReader + BufWriter combination.

Comments