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