Display
fmt::Debug의 출력은 별로 깔끔하지 않기 때문에, 출력 형태를 별도로 구현해야 하는
경우가 많습니다. 이때는 fmt::Display 트레잇을 구현하면 {} 마커로
출력할 수 있습니다. 구현하는 방법은 다음과 같습니다.
#![allow(unused)]
fn main() {
// fmt 모듈을 사용하기 위해 use 키워드로 임포트합니다.
use std::fmt;
// fmt::Display 를 구현할 구조체입니다. Structure 라는 구조체에 한 개의 i32만 넣었습니다.
struct Structure(i32);
// {} 를 이용해 출력하려면 해당 자료영에 대해 fmt::Display 트레잇이 구현되어 있어야 합니다.
impl fmt::Display for Structure {
// 이 트레잇에는 정확한 형식의 fmt 함수가 있어야 합니다.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// 첫번째 요소를 출력 버퍼 f 에 씁니다.
// 리턴값은 fmt::Result 인데, 동작의 성공 여부를 나타냅니다.
// write! 는 println! 과 유사한 문법을 사용합니다.
write!(f, "{}", self.0)
}
}
}
fmt::Display 를 쓰는 편이 fmt::Debug 의 경우 보다 더 깔끔합니다만,
표준(std) 라이브러리에 구현하기에는 어려움이 있습니다. 출력 형식을 지정하기 애매한 자료형 때문입니다.
예를 들어, 표준 라이브러리에서 모든 Vec<T> 에 대해 출력방식을 구현한다면
어떤 식으로 해야 할까요? 다음 두가지 중에 어느쪽이 적절할까요?
Vec<path>:/:/etc:/home/username:/bin(:로 나눠서 표시하기)Vec<number>:1,2,3(,로 나눠서 표시하기)
둘 다 안됩니다. 모든 자료형을 위한 이상적인 한가지 출력형식이란 있을 수 없고,
표준라이브러리가 어떤 한가지 방식을 강제해서도 안되기 때문입니다. fmt::Display 는
Vec<T> 나 다른 제네릭 컨테이너에 대해서는 구현되어있지 않습니다. 이런 경우에는
fmt::Debug를 사용하면 됩니다.
이것이 큰 문제는 되지 않습니다. 제네릭이 아닌 모든 새로운 컨테이너 자료형은
fmt::Display를 구현하면 되기 때문입니다.
use std::fmt; // fmt를 임포트합니다.
// 숫자 두개를 가진 구조체입니다. Debug 파생 구현도 만들어서, Display 구현과
// 비교해봅시다.
#[derive(Debug)]
struct MinMax(i64, i64);
// MinMax 를 위한 Display 구현입니다.
impl fmt::Display for MinMax {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// self.number로 해당 위치의 데이터를 가리킵니다.
write!(f, "({}, {})", self.0, self.1)
}
}
// 비교를 위해 이름이 있는 필드들로 구조체를 만듭시다.
#[derive(Debug)]
struct Point2D {
x: f64,
y: f64,
}
// 역시 Point2D 의 Display 도 구현합니다.
impl fmt::Display for Point2D {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// x와 y만 표시하도록 합니다.
write!(f, "x: {}, y: {}", self.x, self.y)
}
}
fn main() {
let minmax = MinMax(0, 14);
println!("구조체의 출력을 비교해봅시다 :");
println!("Display: {}", minmax);
println!("Debug: {:?}", minmax);
let big_range = MinMax(-300, 300);
let small_range = MinMax(-3, 3);
println!("넓은 범위는 {big}이고, 좁은 범위는 {small}입니다.",
small = small_range,
big = big_range);
let point = Point2D { x: 3.3, y: 7.2 };
println!("포인트 구조체도 비교합시다 :");
println!("Display: {}", point);
println!("Debug: {:?}", point);
// 다음 코드에서는 컴파일 오류가 납니다. Debug와 Display는 구현되었지만,
// {:b}는 fmt::Binary 구현이 필요하기 때문입니다.
// println!("Point2D 를 이진수로 출력하면 어떻게 될까: {:b}?", point);
}
fmt::Display 는 구현했지만 fmt::Binary 는 안했기 때문에 {:b} 는 사용할 수 없습니다.
std::fmt 에는 많은 트레잇(traits) 이 있고 각각을 구현해주어야 합니다.
더 자세한 사항은 std::fmt 을 보아주세요.
실습
위의 출력을 확인하시고, Point2D 구조체를 참고해서 복소수(Complex 라고 명명하세요)
구조체를 만들어서 출력하면 다음처럼 나오도록 구현해보세요.
Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }
참고:
파생 구현(derive), std::fmt, 매크로(macros),
구조체(struct), 트레잇(trait), use