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

impl Trait

TicketStore::to_dosVec<&Ticket>을 반환합니다. 하지만 이 시그니처는 to_dos가 호출될 때마다 새로운 힙 할당을 발생시키는데, 호출자가 결과물을 가지고 무엇을 하느냐에 따라 이는 불필요한 낭비가 될 수 있습니다. 만약 to_dosVec 대신 반복자(Iterator)를 직접 반환한다면, 호출자가 이를 다시 Vec으로 모을지 아니면 그저 루프를 돌며 소비할지 스스로 결정할 수 있어 더 효율적일 것입니다.

하지만 여기엔 한 가지 까다로운 점이 있습니다. 아래처럼 구현했을 때, to_dos의 반환 타입은 정확히 무엇이 되어야 할까요?

impl TicketStore {
    pub fn to_dos(&self) -> ??? {
        self.tickets.iter().filter(|t| t.status == Status::ToDo)
    }
}

이름 없는 타입

filter 메서드는 std::iter::Filter 구조체의 인스턴스를 반환하며, 그 정의는 다음과 같습니다.

pub struct Filter<I, P> { /* 필드 생략 */ }

여기서 I는 필터링할 반복자의 타입이고, P는 필터링 조건으로 사용되는 조건자(Predicate)입니다. 이 경우 Istd::slice::Iter<'_, Ticket>이라는 것은 알 수 있지만, P는 무엇일까요? P는 우리가 전달한 클로저, 즉 익명 함수입니다. 이름 그대로 “이름이 없는” 함수이기 때문에, 우리가 직접 코드에 그 타입 이름을 적어줄 방법이 없습니다.

Rust는 이 문제를 해결하기 위해 **impl Trait**라는 기능을 제공합니다.

impl Trait

impl Trait는 구체적인 타입 이름을 명시하지 않고도 특정 트레이트를 구현한 타입을 반환할 수 있게 해주는 기능입니다. 반환할 타입이 어떤 트레이트를 만족하는지만 선언하면, 나머지는 Rust 컴파일러가 알아서 처리합니다.

우리 예제에서는 Ticket에 대한 참조를 내뱉는 반복자를 반환하고 싶으므로 다음과 같이 작성할 수 있습니다.

impl TicketStore {
    pub fn to_dos(&self) -> impl Iterator<Item = &Ticket> {
        self.tickets.iter().filter(|t| t.status == Status::ToDo)
    }
}

간단하죠!

제네릭과 다른 점

반환 위치에 쓰인 impl Trait는 제네릭 매개변수가 아닙니다.

제네릭은 함수를 호출하는 쪽에서 타입을 결정하는 자리 표시자입니다. 따라서 제네릭 매개변수가 있는 함수는 **다형적(Polymorphic)**입니다. 즉, 서로 다른 타입으로 호출될 수 있으며 컴파일러는 각 타입에 맞는 구현을 각각 생성합니다.

반면 impl Trait는 그렇지 않습니다. impl Trait를 사용하는 함수의 반환 타입은 컴파일 시점에 단 하나의 타입으로 고정되며, 컴파일러는 그에 대한 단일 구현만을 만듭니다. 호출하는 쪽에서는 반환되는 값의 구체적인 타입을 알 수 없고 단지 지정된 트레이트를 구현하고 있다는 사실만 알 수 있기 때문에, 이를 **불투명 반환 타입(Opaque return type)**이라고도 부릅니다. 비록 호출자에게는 불투명하지만 컴파일러는 실제 타입을 정확히 알고 있으므로 다형성과는 거리가 있습니다.

RPIT

Rust 관련 기술 문서나 RFC를 읽다 보면 RPIT라는 용어를 마주칠 수 있습니다. 이는 **“Return Position Impl Trait”**의 약자로, 말 그대로 반환 위치에서 사용된 impl Trait를 의미합니다.

Exercise

The exercise for this section is located in 06_ticket_management/08_impl_trait