Rust 기초: 출력 매크로와 Display, Debug 트레잇
Rust를 배우다 보면 화면에 무언가를 출력하기 위해 println!
같은 매크로를 많이 사용하게 됩니다.
그런데 이 매크로들 뒤에는 Display
와 Debug
라는 트레잇이 숨어 있고, 이 둘의 차이를 이해하면 Rust의 출력 메커니즘을 더 깊이 있게 다룰 수 있습니다.
출력 매크로
Rust에는 표준 출력 및 에러 출력을 위한 다양한 매크로(macro)가 있습니다. 아래는 가장 많이 사용하는 출력 관련 매크로들입니다.
println!
: 표준 출력(stdout)에 줄바꿈과 함께 문자열을 출력합니다print!
: 줄바꿈 없이 출력합니다eprintln!
: 표준 에러(stderr)에 줄바꿈과 함께 출력합니다eprint!
: 줄바꿈 없이 stderr에 출력합니다format!
: 문자열을 반환만 하고, 실제 출력하지 않습니다
fn main() {
let name = "Rust";
println!("Hello, {}!", name); // stdout에 출력
eprintln!("Oops, something broke"); // stderr에 출력
let msg = format!("Hello, {}!", name); // 문자열 생성만
println!("{}", msg);
}
출력 포멧
Rust 출력 포멧은 매우 강력합니다.
{}
안에 다양한 포맷 옵션을 줄 수 있어 정렬, 숫자 진수 출력, 부동소수점 자릿수 지정 등 다양한 출력 포맷을 조절할 수 있습니다.
fn main() {
let num = 42;
println!("기본 출력: {}", num);
println!("너비 지정: {:5}", num); // " 42"
println!("0으로 채우기: {:05}", num); // "00042"
println!("진수 출력: {:x}", num); // "2a"
println!("정렬: {:>6}", num); // 오른쪽 정렬
println!("정렬: {:<6}", num); // 왼쪽 정렬
println!("정렬: {:^6}", num); // 가운데 정렬
}
컴파일 오류
기본 자료형이 아닌 struct
이나 enum
을 출력하려고 하면 다음과 같은 컴파일 오류가 발생하는데요.
struct User {
id: u32,
name: String,
}
fn main() {
let user = User {
id: 1,
name: String::from("Dale"),
};
println!("{}", user);
// ^^^^ `User` cannot be formatted with the default formatter
}
이러한 문제가 발생하는 이유는 사용자가 정의한 자료형을 어떻게 출력해야 할지 모르기 때문입니다.
Display 트레잇
Display 트레잇(trait)의 fmt()
함수를 구현하면 자료형을 어떻게 출력할지 알려줄 수 있습니다.
use std::fmt;
struct User {
id: u32,
name: String,
}
impl fmt::Display for User { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ID: {}, 이름: {}", self.id, self.name) }}
fn main() {
let user = User {
id: 1,
name: String::from("Dale"),
};
println!("{}", user);
}
ID: 1, 이름: Dale
참고로 Display
트레잇을 구현하면 Rust는 자동으로 ToString
트레잇도 구현해주기 때문에, .to_string()
메서드도 사용할 수 있습니다.
Debug 트레잇
매번 Display
트레잇(trait)을 구현하는 것이 번거로울 수도 있습니다.
만약에 단순히 디버깅 용도로 출력이 필요하다면 대신 Debug
트레잇을 활용할 수 있습니다.
struct
이나 enum
위에 #[derive(Debug)]
를 붙여주면 자동으로 파생(derive)가 되서 매우 편리합니다.
그리고 출력 포멧은 {}
대신에 {:?}
또는 {:#?}
를 사용해야 합니다.
#[derive(Debug)]struct User {
id: u32,
name: String,
}
fn main() {
let user = User {
id: 1,
name: String::from("Dale"),
};
println!("{:?}", user); // 한 줄 출력 println!("{:#?}", user); // 예쁘게 출력}
{}
안에 다양한 포맷 옵션을 줄 수 있어 정렬, 숫자 진수 출력, 부동소수점 자릿수 지정 등 다양한 출력 포맷을 조절할 수 있습니다.
Display vs. Debug
언제 Display
트레잇을 구현하고, 언제 Debug
트레잇을 파생하면 좋을까요?
사람이 읽기 좋은 형식으로 데이터를 출력하고 싶다면 Display
트레잇을 직접 구현하는 것이 좋습니다.
반면에 개발자가 디버깅 용도로 데이터를 자세히 들여다보기 위한 출력할 때는 Debug
트레잇을 자동 파생시키는 편이 유리합니다.
구분 | 목적 | 사용 방식 | 자동 구현 가능 |
---|---|---|---|
Display |
사용자 친화적 출력 | {} |
❌ (직접 구현 필요) |
Debug |
디버깅 용도 출력 | {:?} , {:#?} |
✅ (#[derive(Debug)] ) |
마치며
Rust의 출력 관련 매크로와 Display, Debug 트레잇은 단순한 텍스트 출력 그 이상입니다. 로깅, 에러 핸들링, CLI 유틸리티 등 다양한 곳에서 활용되며, 특히 Rust의 엄격한 타입 시스템과 잘 어우러져 실수 없는 출력을 가능하게 해줍니다.
앞으로 println!
을 사용할 때마다 이 매크로 뒤에 숨어 있는 트레잇과 포맷팅 시스템을 떠올려 보세요.
Rust의 출력을 마스터하면, 여러분의 코드도 더 깔끔하고 명확해질 것입니다.