블로그로 돌아가기

[Rust] 입력받고 출력하기

(수정됨: 2026년 3월 10일)

알고리즘 문제를 Rust로 풀 때 가장 먼저 부딪히는 게 입출력이다. C++의 cin/cout, Python의 input()/print()처럼 간단하진 않지만, 한번 익혀두면 계속 쓸 수 있다.

기본 출력: println!

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

가장 간단한 출력. 매 호출마다 stdout을 lock하고 flush하기 때문에 출력이 많으면 느리다.

빠른 출력: 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();
}

BufWriter로 stdout을 감싸면 버퍼에 모아서 한번에 출력한다. 출력이 많은 문제에서 수십 배 빠르다.

  • stdout.lock() → stdout lock을 한 번만 잡음
  • BufWriter → 버퍼링으로 시스템 콜 횟수를 줄임
  • writeln!println! 대신 사용하는 매크로

기본 입력: 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);
}

한 줄만 읽을 때 쓸 수 있다. 간단하지만 여러 줄 읽을 때는 번거롭다.

빠른 입력: 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_stringEOF까지 전체 입력을 한번에 읽는다. 그래서 터미널에서 직접 실행할 때는 입력 후 Ctrl+Z(Windows) 또는 Ctrl+D(Linux/Mac)를 눌러야 한다. 백준 같은 온라인 저지에서는 자동으로 EOF가 들어오니 신경 쓸 필요 없다.

split_whitespace()는 공백, 탭, 줄바꿈을 모두 구분자로 처리해서 파싱이 편하다.

두 수 입력받아 더하기 (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();
}

unwrap()이 뭔데?

writeln!이나 parse() 같은 함수는 Result 타입을 반환한다.

  • 성공 (Ok(값)) → unwrap()이 값을 꺼내줌
  • 실패 (Err(에러)) → unwrap()이 panic으로 프로그램 종료

알고리즘 문제에서는 입출력이 실패할 일이 없으니 unwrap()으로 간단히 처리한다. 실제 프로덕션 코드에서는 ? 연산자로 에러를 전파하는 게 더 좋다.

mut와 &mut는 뭔데?

변수 선언할 때 mut

Rust는 변수가 기본적으로 불변(immutable)이다. 값을 바꾸려면 mut를 붙여야 한다.

Rust
let input = String::new();       // 불변 → 값 변경 불가
let mut input = String::new();   // 가변 → 값 변경 가능

read_to_stringinput에 데이터를 써넣어야 하니까 mut가 필요하다.

함수에 넘길 때 &mut

&는 참조(reference)다. 값을 복사하지 않고 빌려주는 것.

  • &input → 읽기만 가능한 참조 (불변 참조)
  • &mut input → 읽기 + 쓰기 가능한 참조 (가변 참조)
Rust
reader.read_to_string(&mut input).unwrap();
//                    ^^^^^^^^^^
// input을 수정해야 하니까 &mut로 넘긴다

read_to_string은 읽은 데이터를 input 안에 채워넣어야 하므로 수정 권한이 있는 &mut로 넘겨야 한다.

lock()은 왜 하는 거야?

stdout()stdin()은 여러 스레드에서 동시에 접근할 수 있다. 그래서 기본적으로 매 호출마다 lock을 잡고 푼다.

Rust
// println!은 내부적으로 매번 이렇게 동작한다
stdout().lock() → 쓰기 → unlock
stdout().lock() → 쓰기 → unlock  // 반복할수록 느림

.lock()을 직접 호출하면 lock을 한 번만 잡고 계속 쓸 수 있다.

Rust
let stdout = io::stdout();
let locked = stdout.lock();           // 한 번만 lock
let mut out = BufWriter::new(locked); // 이후 계속 사용

stdin도 마찬가지다. stdin().lock()으로 lock을 한 번 잡아두면 입력을 읽는 동안 반복적인 lock/unlock이 없어서 빠르다.

next()는 뭐야?

split_whitespace()는 iterator(반복자)를 반환한다. iterator는 값을 하나씩 순서대로 꺼낼 수 있는 객체다.

.next()를 호출할 때마다 다음 값을 하나씩 꺼낸다.

Rust
let input = "1 2 3";
let mut iter = input.split_whitespace();

iter.next()  // → Some("1")  첫 번째 값
iter.next()  // → Some("2")  두 번째 값
iter.next()  // → Some("3")  세 번째 값
iter.next()  // → None       더 이상 없음

SomeNoneOption 타입이다. 값이 있을 수도, 없을 수도 있다는 뜻이다. unwrap()으로 Some 안의 값을 꺼낸다.

Rust
iter.next()           // Some("1")
iter.next().unwrap()  // "1" ← Some을 벗겨낸 값

parse()는 뭐야?

parse()는 문자열을 원하는 타입으로 변환하는 메서드다. Python의 int(), C++의 stoi()와 같은 역할이다.

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

변환할 타입은 변수의 타입 선언으로 지정한다.

Rust
let a: i32 = "42".parse().unwrap();    // 정수 32비트
let b: i64 = "42".parse().unwrap();    // 정수 64비트
let c: f64 = "3.14".parse().unwrap();  // 실수 64비트
let d: usize = "42".parse().unwrap();  // 부호 없는 정수 (배열 인덱스 등)

변환에 실패하면 Err를 반환하고, unwrap()이 panic을 일으킨다.

Rust
let n: i32 = "abc".parse().unwrap();  // panic! 숫자가 아님

전체 흐름 정리

입력 "1 2"를 두 정수로 파싱하는 과정을 한 줄씩 따라가 보면:

Rust
let mut iter = input.split_whitespace();
// iter: ["1", "2"]를 순서대로 꺼낼 수 있는 iterator

let a: i32 = iter.next().unwrap().parse().unwrap();
//           ^^^^^^^^^^^  → Some("1")
//                        ^^^^^^^^^  → "1"
//                                    ^^^^^^^  → Result<i32, _>
//                                              ^^^^^^^^^  → 1 (i32)

next()로 문자열을 꺼내고 → unwrap()으로 Some을 벗기고 → parse()로 숫자로 변환하고 → unwrap()으로 Result를 벗긴다.

정리

느린 방법빠른 방법
입력stdin().read_line()BufReader + read_to_string
출력println!BufWriter + writeln!

알고리즘 문제 풀 때는 그냥 항상 BufReader + BufWriter 조합을 쓰자.

댓글