1278 words
6 minutes
260111_APIT_RPIT_trait

link#


Summary|🔝|#

  • Below is a clear, precise explanation of APIT vs RPIT, with Rust code, mental models, and why this distinction matters for async, closures, and GATs.

    • 아래는 Rust 코드, 정신 모델(mental models), 비동기(async), 폐쇄(closures) 및 GAT에 대해 APIT와 RPIT의 명확하고 정확한 설명입니다.
  • 한 문장 요약

APIT is caller-chosen generics; RPIT is callee-chosen opaque types.

APIT는 호출자가 선택한 제네릭이고, RPIT는 호출자가 선택한 불투명 타입입니다.

APIT vs RPIT in Rust|🔝|#

  • The core question

  • Where does impl Trait (or an opaque type) appear?

    • Argument position → APIT
    • Return position → RPIT
  • This affects:

    • who chooses the concrete type
    • monomorphization
    • type identity
    • lifetime capture
    • async / closure behavior
  • 러스트에서 APIT 대 RPIT

    • 핵심 질문

    • impl Trait(또는 불투명한 유형)은 어디에 나타나나요?

      • Argument position(인수 위치) → APIT
      • Return position(복귀 위치) → RPIT
    • 이것은 영향을 미칩니다:

      • 콘크리트 유형을 선택하는 사람
      • 단일화
      • 타입 아이덴티티
      • 평생 포획

1️⃣ APIT — Argument-Position impl Trait|🔝|#

  • Definition
fn f(x: impl Iterator<Item = i32>) {
    for v in x {
        println!("{v}");
    }
}
  • What it means
    • ➡️ Caller chooses the concrete type
  • This is syntax sugar for:
fn f<I: Iterator<Item = i32>>(x: I) {
    for v in x {
        println!("{v}");
    }
}
  • Key properties
PropertyAPIT
Who picks the type?Caller
Generic?
Monomorphized?
Type identity stable?
(depends on caller)
Can capture lifetimes?

APIT example: multiple callers#

fn consume(it: impl Iterator<Item = i32>) {
    for x in it {
        println!("{x}");
    }
}

fn main() {
    consume(vec![1, 2, 3].into_iter());
    consume(0..5);
}
  • Each call instantiates a different monomorphized version.
    • 각 호출은 서로 다른 단일화된 버전을 인스턴스화합니다.

  • APIT mental model

“I accept any type that implements this trait.”

  • APIT is just a generic parameter with nicer syntax.

  • APIT 정신 모델

“저는 이 특성을 구현하는 모든 유형을 받아들입니다.”

  • APIT는 더 나은 구문을 가진 일반적인 매개변수일 뿐입니다.

2️⃣ RPIT — Return-Position impl Trait|🔝|#

  • Definition
fn make_iter() -> impl Iterator<Item = i32> {
    0..5
}
  • What it means

    • ➡️ Function chooses the concrete type
  • The caller:

    • does not know
    • cannot name
    • cannot change
    • the actual return type.
  • 의미

    • ➡️ 기능은 콘크리트 유형을 선택합니다
  • 발신자:

    • 모릅니다
    • 이름을 지정할 수 없습니다
    • 변경할 수 없습니다
    • 실제 반품 유형.

  • Desugaring (conceptual)
    • 디슈가링 (개념적)
type __MakeIterReturn = std::ops::Range<i32>;

fn make_iter() -> __MakeIterReturn {
    0..5
}
  • 📌 This hidden type is fixed per function.
    • 📌 이 숨겨진 유형은 기능별로 고정되어 있습니다.

  • RPIT rules
fn bad(cond: bool) -> impl Iterator<Item = i32> {
    if cond {
        0..3
    } else {
        vec![1, 2, 3].into_iter() // ❌ different type
    }
}
  • ✔ All return paths must produce the same concrete type

    • ✔ 모든 반환 경로는 동일한 콘크리트 유형을 생성해야 합니다
  • RPIT mental model

“I return some specific type, but you’re not allowed to know which.”

  • RPIT 정신 모델

“특정 유형을 반환하지만 어떤 유형인지 알 수 없습니다.”

3️⃣ APIT vs RPIT — Side-by-Side(표로 비교)|🔝|#

FeatureAPITRPIT
PositionArgumentReturn
Who chooses typeCallerCallee
Desugars toGeneric paramHidden concrete type
Type identityPer callPer function
Can differ per call
Used by async fn
Can capture lifetimes

4️⃣ Why async fn Uses RPIT|🔝|#

  • 4️⃣ 비동기 fn이 RPIT를 사용하는 이유
async fn compute() -> i32 {
    5
}
  • Desugars to:
fn compute() -> impl Future<Output = i32> {
    async move { 5 }
}
  • ✔ The future type is:

    • compiler-generated
    • unique
    • unnamed
    • captures locals
  • 📌 This is RPIT

  • ✔ 미래의 유형은:

    • 컴파일러 생성
    • 독특한
    • 이름 없는
    • 현지인을 사로잡습니다
  • 📌 RPIT입니다

5️⃣ Why APIT Cannot Work for async fn|🔝|#

  • 5️⃣ 비동기 FN에서 APIT가 작동하지 않는 이유

  • ❌ Invalid:

    • 무효한
fn compute(f: impl Future<Output = i32>) -> i32 {
    // impossible
}
  • Async requires:

    • returning a future
    • that captures locals
    • whose concrete type is hidden
  • Only RPIT supports this

  • 비동기화에는 다음이 필요합니다:

    • 미래를 되돌리기
    • 현지인을 사로잡는
    • 구체적인 유형이 숨겨져 있는 사람
  • RPIT만이 이를 지원합니다

6️⃣ RPIT + Lifetimes (Critical Difference)|🔝|#

6️⃣ RPIT + 수명 (중요한 차이)

RPIT can capture lifetimes#

  • RPIT는 수명을 포착할 수 있습니다
fn slice<'a>(s: &'a [i32]) -> impl Iterator<Item = &'a i32> {
    s.iter()
}
  • ✔ The returned type contains 'a

APIT cannot express this#

fn consume(it: impl Iterator<Item = &i32>) {
    // no way to tie lifetime
}

7️⃣ RPIT in Traits (RPITIT)|🔝|#

trait Factory {
    fn make(&self) -> impl Iterator<Item = i32>;
}
  • ✔ Each implementor chooses its own hidden type
  • ❌ Not object-safe
    • ✔ 각 구현자는 자신만의 숨겨진 유형을 선택합니다
    • ❌ 물건에 안전하지 않음

8️⃣ GATs vs RPIT (Important!)|🔝|#

  • RPIT returns one fixed type

    • RPIT는 하나의 고정된 유형을 반환합니다
  • GATs return types parameterized by lifetimes

    • GAT는 수명으로 매개변수화된 유형을 반환합니다
trait LendingIter {
    type Item<'a>
    where
        Self: 'a;

    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}
  • 📌 RPIT cannot replace GATs
    • 📌 RPIT는 GAT를 대체할 수 없습니다

9️⃣ When to Use Which|🔝|#

  • Use APIT when:

    • accepting flexible inputs
    • Writing generic helpers
    • You want caller freedom
  • Use RPIT when:

    • Returning complex or unnamed types
    • Writing async fn
    • Hiding implementation details
    • Enabling zero-cost abstractions
  • 다음과 같은 경우 APIT 사용:

    • 유연한 입력 수락
    • 일반 도우미 작성하기
    • 발신자의 자유를 원합니다
  • RPIT는 다음과 같은 경우에 사용합니다:

    • 복소수 또는 이름 없는 유형 반환
    • 비동기 fn 작성하기
    • 구현 세부 정보 숨기기
    • 제로 비용 추상화 활성화

🔟 One-Sentence Summary|🔝|#

  • 🔟 한 문장 요약

APIT is caller-chosen generics; RPIT is callee-chosen opaque types.

APIT는 호출자가 선택한 제네릭이고, RPIT는 호출자가 선택한 불투명 타입입니다.

260111_APIT_RPIT_trait
https://younghakim7.github.io/blog/posts/260111_apit_rpit_trait/
Author
YoungHa
Published at
2026-01-11