2장: 고급 타입 시스템

2.1 Newtype 패턴

Newtype 패턴은 기존 타입을 감싸서 새로운 타입을 만듭니다. 타입 안전성을 높이고 의미를 명확히 합니다.

2.1.1 기본 Newtype

// 미터와 피트를 혼동하지 않도록!
struct Meters(f64);
struct Feet(f64);

impl Meters {
    fn to_feet(&self) -> Feet {
        Feet(self.0 * 3.28084)
    }
}

impl Feet {
    fn to_meters(&self) -> Meters {
        Meters(self.0 / 3.28084)
    }
}

fn calculate_distance(m: Meters) -> f64 {
    m.0
}

fn main() {
    let height = Meters(100.0);
    let height_feet = height.to_feet();

    // 타입 체크로 실수 방지!
    // calculate_distance(height_feet); // 컴파일 에러!
    calculate_distance(height); // OK
}

2.1.2 트레이트 우회하기

use std::fmt;

// Vec<String>에 Display 구현 불가 (orphan rule)
// Newtype으로 해결!
struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![
        String::from("hello"),
        String::from("world"),
    ]);
    println!("w = {}", w);
}

2.2 타입 별칭 (Type Alias)

복잡한 타입에 간단한 이름을 부여합니다.

// 복잡한 타입 단순화
type Kilometers = i32;
type Thunk = Box<dyn Fn() + Send + 'static>;

// Result 타입 특화
type Result<T> = std::result::Result<T, std::io::Error>;

fn read_file() -> Result<String> {
    std::fs::read_to_string("file.txt")
}

// 복잡한 함수 포인터
type FnPointer = fn(i32) -> i32;

fn apply(f: FnPointer, x: i32) -> i32 {
    f(x)
}

fn main() {
    let distance: Kilometers = 5;

    let thunk: Thunk = Box::new(|| println!("Hello!"));
    thunk();

    let double: FnPointer = |x| x * 2;
    println!("{}", apply(double, 5));
}

2.3 PhantomData와 제로 코스트 추상화

컴파일 타임에만 존재하고 런타임에는 사라지는 타입 마커입니다.

use std::marker::PhantomData;

// 타입 상태 패턴
struct Locked;
struct Unlocked;

struct Door<State> {
    id: u32,
    _state: PhantomData<State>,
}

impl Door<Locked> {
    fn new(id: u32) -> Self {
        Door {
            id,
            _state: PhantomData,
        }
    }

    fn unlock(self) -> Door<Unlocked> {
        println!("Door {} unlocked", self.id);
        Door {
            id: self.id,
            _state: PhantomData,
        }
    }
}

impl Door<Unlocked> {
    fn open(&self) {
        println!("Door {} opened", self.id);
    }

    fn lock(self) -> Door<Locked> {
        println!("Door {} locked", self.id);
        Door {
            id: self.id,
            _state: PhantomData,
        }
    }
}

fn main() {
    let door = Door::new(1);
    // door.open(); // 컴파일 에러! Locked 상태에서는 불가능

    let door = door.unlock();
    door.open(); // OK!

    let door = door.lock();
    // door.open(); // 다시 컴파일 에러!
}
💡 타입 상태 패턴의 장점:

2.4 고급 제네릭

2.4.1 연관 상수

trait Container {
    const MAX_SIZE: usize;
    type Item;

    fn add(&mut self, item: Self::Item);
    fn is_full(&self) -> bool;
}

struct Stack<T> {
    items: Vec<T>,
}

impl<T> Container for Stack<T> {
    const MAX_SIZE: usize = 100;
    type Item = T;

    fn add(&mut self, item: T) {
        if !self.is_full() {
            self.items.push(item);
        }
    }

    fn is_full(&self) -> bool {
        self.items.len() >= Self::MAX_SIZE
    }
}

2.4.2 제네릭 특화 (Specialization)

// nightly에서만 사용 가능
#![feature(specialization)]

trait Print {
    fn print(&self);
}

impl<T> Print for T {
    default fn print(&self) {
        println!("Generic print");
    }
}

impl Print for i32 {
    fn print(&self) {
        println!("i32: {}", self);
    }
}

impl Print for String {
    fn print(&self) {
        println!("String: {}", self);
    }
}

2.5 고급 패턴 매칭

2.5.1 @ 바인딩

enum Message {
    Hello { id: i32 },
}

fn main() {
    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => println!("Found an id in range: {}", id_variable),
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => println!("Found some other id: {}", id),
    }
}

2.5.2 매치 가드

fn main() {
    let num = Some(4);
    let split = 2;

    match num {
        Some(x) if x < 5 => println!("less than five: {}", x),
        Some(x) if x % split == 0 => println!("divisible by {}: {}", split, x),
        Some(x) => println!("{}", x),
        None => (),
    }
}

2.6 Never 타입 (!)

절대 반환하지 않는 함수의 타입입니다.

// 무한 루프
fn infinite_loop() -> ! {
    loop {
        println!("Forever!");
    }
}

// panic!도 ! 타입을 반환
fn divide(x: i32, y: i32) -> i32 {
    if y == 0 {
        panic!("Division by zero");
    }
    x / y
}

// continue도 ! 타입
fn process_numbers(numbers: Vec<i32>) {
    for num in numbers {
        let result = match num {
            0 => continue, // ! 타입
            n if n < 0 => panic!("Negative!"), // ! 타입
            n => n * 2,
        };
        println!("{}", result);
    }
}

2.7 실전 예제: 타입 안전한 빌더

use std::marker::PhantomData;

// 빌더 상태
struct No;
struct Yes;

struct UserBuilder<HasName, HasEmail> {
    name: Option<String>,
    email: Option<String>,
    age: Option<u32>,
    _has_name: PhantomData<HasName>,
    _has_email: PhantomData<HasEmail>,
}

impl UserBuilder<No, No> {
    fn new() -> Self {
        UserBuilder {
            name: None,
            email: None,
            age: None,
            _has_name: PhantomData,
            _has_email: PhantomData,
        }
    }
}

impl<E> UserBuilder<No, E> {
    fn name(self, name: String) -> UserBuilder<Yes, E> {
        UserBuilder {
            name: Some(name),
            email: self.email,
            age: self.age,
            _has_name: PhantomData,
            _has_email: PhantomData,
        }
    }
}

impl<N> UserBuilder<N, No> {
    fn email(self, email: String) -> UserBuilder<N, Yes> {
        UserBuilder {
            name: self.name,
            email: Some(email),
            age: self.age,
            _has_name: PhantomData,
            _has_email: PhantomData,
        }
    }
}

impl<N, E> UserBuilder<N, E> {
    fn age(mut self, age: u32) -> Self {
        self.age = Some(age);
        self
    }
}

// build는 필수 필드가 모두 설정된 경우에만 가능!
impl UserBuilder<Yes, Yes> {
    fn build(self) -> User {
        User {
            name: self.name.unwrap(),
            email: self.email.unwrap(),
            age: self.age,
        }
    }
}

struct User {
    name: String,
    email: String,
    age: Option<u32>,
}

fn main() {
    let user = UserBuilder::new()
        .name("Alice".to_string())
        .email("alice@example.com".to_string())
        .age(30)
        .build(); // OK!

    // 컴파일 에러 예제:
    // let user = UserBuilder::new()
    //     .name("Bob".to_string())
    //     .build(); // 에러! email 필요
}
🎯 핵심 요약: