테스트케이스: 단위 명확화
팬텀 타입 파라미터로 Add를 구현하여 유용한 단위 변환 방법을 살펴볼 수 있습니다. Add 트레이트는 아래에서 살펴봅니다:
// 이 구조는 다음을 부과합니다: `Self + RHS = Output`
// 구현에서 지정되지 않은 경우 RHS는 기본적으로 Self입니다.
pub trait Add<RHS = Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
// `Output`은 `T<U> + T<U> = T<U>`가 되도록 반드시 `T<U>`여야 합니다.
impl<U> Add for T<U> {
type Output = T<U>;
...
}
전체 구현:
use std::ops::Add;
use std::marker::PhantomData;
/// 단위 타입을 정의하기 위해 빈 열거형을 생성합니다.
#[derive(Debug, Clone, Copy)]
enum Inch {}
#[derive(Debug, Clone, Copy)]
enum Mm {}
/// `Length`는 팬텀 타입 파라미터 `Unit`을 가지는 타입이며,
/// 길이 타입(`f64`)에 대해서는 제네릭이 아닙니다.
///
/// `f64`는 이미 `Clone`과 `Copy` 트레이트를 구현하고 있습니다.
#[derive(Debug, Clone, Copy)]
struct Length<Unit>(f64, PhantomData<Unit>);
/// `Add` 트레이트는 `+` 연산자의 동작을 정의합니다.
impl<Unit> Add for Length<Unit> {
type Output = Length<Unit>;
// add()는 합계를 포함하는 새로운 `Length` 구조체를 반환합니다.
fn add(self, rhs: Length<Unit>) -> Length<Unit> {
// `+`는 `f64`에 대한 `Add` 구현을 호출합니다.
Length(self.0 + rhs.0, PhantomData)
}
}
fn main() {
// `one_foot`가 팬텀 타입 파라미터 `Inch`를 가지도록 지정합니다.
let one_foot: Length<Inch> = Length(12.0, PhantomData);
// `one_meter`는 팬텀 타입 파라미터 `Mm`을 가집니다.
let one_meter: Length<Mm> = Length(1000.0, PhantomData);
// `+`는 우리가 `Length<Unit>`에 대해 구현한 `add()` 메서드를 호출합니다.
//
// `Length`가 `Copy`를 구현하므로, `add()`는 `one_foot`과 `one_meter`를
// 소비(consume)하지 않고 `self`와 `rhs`로 복사합니다.
let two_feet = one_foot + one_foot;
let two_meters = one_meter + one_meter;
// 덧셈이 작동합니다.
println!("1피트 + 1피트 = {:?} 인치", two_feet.0);
println!("1미터 + 1미터 = {:?} mm", two_meters.0);
// 말도 안 되는 연산은 실패합니다:
// 컴파일 타임 에러: 타입 불일치.
// let one_feter = one_foot + one_meter;
}
참고:
빌림(Borrowing) (&), 바운드(Bounds) (X: Y), 열거형(enum), impl & self, 오버로딩(Overloading), ref, 트레이트(Traits) (X for Y), 그리고 튜플 구조체(TupleStructs).