Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

디스플레이

fmt::Debug는 콤팩트하고 깔끔해 보이지 않으므로, 출력 모양을 커스터마이징하는 것이 종종 유리합니다. 이는 {} 출력 마커를 사용하는 fmt::Display를 수동으로 구현함으로써 수행됩니다. 구현은 다음과 같습니다:

#![allow(unused)]
fn main() {
// `fmt` 모듈을 (`use`를 통해) 가져와서 사용할 수 있게 합니다.
use std::fmt;

// `fmt::Display`를 구현할 구조체를 정의합니다. 이것은
// `i32`를 포함하는 `Structure`라는 이름의 튜플 구조체입니다.
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::Displayfmt::Debug보다 깔끔할 수 있지만, 이는 std 라이브러리에 문제를 제기합니다. 모호한 타입들은 어떻게 표시되어야 할까요? 예를 들어, std 라이브러리가 모든 Vec<T>에 대해 단일 스타일을 구현했다면, 어떤 스타일이어야 할까요? 다음 두 가지 중 하나일까요?

  • Vec<path>: /:/etc:/home/username:/bin (:로 구분)
  • Vec<number>: 1,2,3 (,로 구분)

아니요, 모든 타입에 이상적인 스타일은 없으며 std 라이브러리가 하나를 독단적으로 정하지 않기 때문입니다. fmt::DisplayVec<T>나 다른 제네릭 컨테이너에 대해 구현되어 있지 않습니다. 따라서 이러한 제네릭 케이스에는 fmt::Debug를 사용해야 합니다.

하지만 제네릭이 아닌 새로운 컨테이너 타입에 대해서는 fmt::Display를 구현할 수 있으므로 이는 문제가 되지 않습니다.

use std::fmt; // `fmt` 가져오기

// 두 숫자를 보유하는 구조체입니다. `Display`와 대조해 볼 수 있도록
// `Debug`를 유도(derive)할 것입니다.
#[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!("디스플레이: {}", minmax);
    println!("디버그: {:?}", 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!("디스플레이: {}", point);
    println!("디버그: {:?}", point);

    // 에러. `Debug`와 `Display`는 모두 구현되었지만, `{:b}`는
    // `fmt::Binary` 구현을 요구합니다. 이것은 작동하지 않을 것입니다.
    // println!("Point2D의 2진수 형태는 무엇일까요: {:b}?", point);
}

따라서, fmt::Display는 구현되었지만 fmt::Binary는 구현되지 않았으므로 사용할 수 없습니다. std::fmt에는 이와 같은 많은 트레이트가 있으며 각각은 자체적인 구현을 요구합니다. 이에 대한 자세한 내용은 std::fmt에 나와 있습니다.

실습

위 예제의 출력을 확인한 후, Point2D 구조체를 가이드로 삼아 예제에 Complex 구조체를 추가해 보세요. 같은 방식으로 출력했을 때의 결과는 다음과 같아야 합니다:

Display: 3.3 +7.2i
Debug: Complex { real: 3.3, imag: 7.2 }

Display: 4.7 -2.3i
Debug: Complex { real: 4.7, imag: -2.3 }

보너스: +/- 기호 앞에 공백을 추가하세요.

막혔을 때를 위한 힌트:

  • std::fmt에서 Sign/#/0에 대한 문서를 확인하세요.
  • 보너스: if-else 분기와 abs 함수를 확인하세요.

참고:

derive, std::fmt, 매크로, 구조체, 트레이트, 그리고 use