Display: 사용자를 위한 미려한 출력 포맷

Display 트레이트는 Debug와는 달리, 최종 사용자(end-user)에게 보여주기 위한 깔끔하고 읽기 좋은 문자열 표현을 정의하기 위해 사용됩니다. println!과 같은 포매팅 매크로에서 {} 지정자를 사용하면 Display 트레이트의 구현이 호출됩니다.

Debug가 “개발자가 무엇을 보고 싶어 하는가?”에 초점을 맞춘다면, Display는 “사용자가 무엇을 봐야 하는가?”에 대한 답을 제공합니다.

Debug vs Display

특징DebugDisplay
목적개발자의 디버깅최종 사용자를 위한 표시
포맷 지정자{:?}, {:#?}{}
구현 방식#[derive]로 자동 구현 가능항상 수동으로 구현해야 함
출력 형태타입 이름과 필드를 명확히 표시간결하고 의미 있는 형태로 자유롭게 정의

타입의 ‘공식적인’ 문자열 표현을 제공하고 싶다면 Display를 구현해야 합니다.

Display 트레이트 수동 구현하기

Display는 어떻게 보여줄지에 대한 의도가 중요하기 때문에 #[derive]로 자동 구현할 수 없습니다. std::fmt 모듈의 Formatter를 사용하여 직접 구현해야 합니다.

Point 구조체를 (x, y) 형식의 좌표로 출력하도록 Display를 구현해 보겠습니다.

use std::fmt;
 
struct Point {
    x: i32,
    y: i32,
}
 
// Point에 대해 Display를 구현합니다.
impl fmt::Display for Point {
    // `f`는 출력이 기록될 버퍼 역할을 하는 Formatter입니다.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // `write!` 매크로를 사용하여 원하는 형식으로 문자열을 `f`에 씁니다.
        // `?` 연산자를 사용하여 쓰기 작업 중 발생할 수 있는 에러를 처리합니다.
        write!(f, "({}, {})", self.x, self.y)
    }
}
 
fn main() {
    let p = Point { x: 10, y: 20 };
 
    // `{}`를 사용하면 Display 구현이 호출됩니다.
    println!("좌표: {}", p);
 
    // `{:?}`를 사용하면 Debug 구현이 필요합니다.
    // println!("디버그: {:?}", p); // Debug를 구현하지 않으면 컴파일 에러
}

출력:

좌표: (10, 20)

ToString 트레이트와의 연동

어떤 타입에 대해 Display를 구현하면, 해당 타입은 자동으로 ToString 트레이트를 만족하게 됩니다. 이는 .to_string() 메서드를 사용하여 해당 객체를 String으로 손쉽게 변환할 수 있음을 의미합니다.

// (위 Point의 Display 구현이 있다고 가정)
 
fn main() {
    let p = Point { x: 10, y: 20 };
 
    // .to_string() 메서드를 사용할 수 있습니다.
    let point_as_string: String = p.to_string();
 
    assert_eq!(point_as_string, "(10, 20)");
    println!("String으로 변환된 좌표: {}", point_as_string);
}

에러 타입과 Display

사용자 정의 에러 타입을 만들 때 Display를 구현하는 것은 매우 좋은 습관입니다. 이를 통해 사용자에게 친절하고 이해하기 쉬운 에러 메시지를 제공할 수 있습니다. 많은 라이브러리와 애플리케이션이 에러를 출력할 때 Display 구현을 사용합니다.

use std::fmt;
 
#[derive(Debug)]
enum MyError {
    FileNotFound(String),
    ConnectionTimeout,
}
 
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MyError::FileNotFound(path) => write!(f, "파일을 찾을 수 없습니다: {}", path),
            MyError::ConnectionTimeout => write!(f, "연결 시간이 초과되었습니다."),
        }
    }
}
// `std::error::Error` 트레이트를 구현하려면 `Debug`와 `Display`가 모두 필요합니다.
impl std::error::Error for MyError {}

결론

Display 트레이트는 당신의 타입을 러스트의 포매팅 시스템에 완벽하게 통합시켜주는 공식적인 창구입니다. Debug가 개발의 편의성을 위한 것이라면, Display는 프로그램의 완성도와 사용자 경험을 높이는 데 기여합니다. .to_string()과의 자동 연동, 에러 메시지 표현 등 다양한 곳에서 활용되므로, 사용자에게 보여줄 필요가 있는 모든 타입에 대해 Display를 구현하는 것을 적극적으로 고려해야 합니다.