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

MIR 타입 검사

Rust의 “정규(canonical)” 타입 검사(라이프타임은 무시함)는 HIR에서 수행됩니다. 그럼에도 우리는 MIR에서도 타입 검사 패스를 수행합니다.

MIR은 완전히 타입이 지정된 중간 표현입니다. 이 시점에는 모든 아이템의 타입과 그 본문의 내용이 알려져 있으며, MIR을 구성했다는 것만으로 그 타입들이 올바르다는 것을 알 수 있습니다. 이미 타입이 지정되고 검사까지 끝난 이 IR에 대해 타입 검사 패스를 다시 수행하는 이유는 빌림 검사에 필요한 라이프타임1에 관한 정보를 수집하기 위함입니다. 참고: borrowck_collect_region_constraints.

이 추가 타입 검사 패스를 수행하면 우리의 작업을 검증할 수도 있습니다. HIR 타입 검사에서는 통과한 것이 MIR 타입 검사에서 실패한다면 무언가 잘못된 것입니다.

“HIR 타입 검사가 성공하면 MIR 타입 검사도 성공해야 한다“는 조건을 유지하는 것은 결코 간단하지 않습니다. 그 주된 이유 중 하나는 MIR 타입 검사 과정에서 기존 라이프타임을 지우고 제약이 없는 새 라이프타임 변수로 대체하는 반면, HIR에서는 라이프타임이 추론되기만 하고 검사되지는 않는다는 점입니다. 이처럼 HIR 타입 검사와 MIR 타입 검사는 각각 미묘하게 다른 정보를 가지고 작업합니다.

MIR에서 라이프타임을 지우고 다시 추론하는 이 전략을 리전 고유화(Region Uniquification)라고 합니다.

리전 고유화

TODO: 리전 고유화와 현재 남아 있는 그 사용처에 대해 더 설명하십시오.

사용자 타입

MIR 타입 검사를 시작할 때 우리는 본문의 모든 리전을 제약이 없는 새 리전으로 대체합니다. 하지만 이렇게 하면 다음 프로그램을 허용하게 됩니다.

#![allow(unused)]
fn main() {
fn foo<'a>(x: &'a u32) {
    let y: &'static u32 = x;
}
}

y의 타입에 있는 라이프타임을 지우면 그것이 'static이어야 한다는 사실을 더 이상 알 수 없게 되어 사용자의 의도를 무시하게 됩니다.

이 문제를 해결하기 위해 우리는 HIR 타입 검사 중에 사용자가 타입을 명시적으로 언급한 모든 위치를 CanonicalUserTypeAnnotations로 기억해 둡니다.

우리가 신경 쓰는 어노테이션은 두 가지입니다.

  • 명시적 타입 지정(type ascription): 예를 들어 let y: &'static u32는 UserType::Ty(&'static u32)가 됩니다.
  • 명시적 제네릭 인자: 예를 들어 x.foo<&'a u32, Vec<String>>는 UserType::TypeOf(foo_def_id, [&'a u32, Vec<String>])가 됩니다.

HIR 타입 검사에서 이루어진 영역 추론이 MIR typeck에 영향을 주지 않도록, 사용자 타입은 HIR에서 로워링한 직후에 저장합니다. 따라서 사용자 타입에 추론 변수가 아직 남아 있을 수 있으며, 이것이 우리가 정규(canonical) 사용자 타입 어노테이션을 사용하는 이유입니다. 대신 우리는 모든 추론 변수를 존재 양화된 바운드 변수로 대체합니다. 따라서 let x: Vec<_>와 같은 것은 exists<T> UserType::Ty(Vec<T>)가 됩니다.

let Foo(x): Foo<&'a u32>와 같은 패턴의 사용자 타입은 Foo<&'a u32>이지만, x의 실제 타입은 &'a u32여야 합니다. 이를 위해 우리는 UserTypeProjection을 사용합니다.

MIR에서는 사용자 타입을 약간 다른 두 가지 방식으로 다룹니다.

명시적 타입 어노테이션이 있는 패턴 안의 변수에 대응하는 MIR 지역 변수(local)가 있으면, 우리는 그 지역 변수의 타입이 UserTypeProjection의 타입과 같아야 한다고 요구합니다. 이 정보는 LocalDecl에 직접 저장됩니다.

또한 스크루티니 표현식(scrutinee expression)의 타입도 제약합니다. 예를 들어 let _: &'a u32 = x;에서 x의 타입이 이에 해당합니다. 여기서 T_x는 사용자 타입의 서브타입이기만 하면 되므로, 이 경우에는 대신 StatementKind::AscribeUserType을 사용합니다.

MIR 타입 검사기는 타입 추론 변수와 상수 추론 변수를 제대로 다루지 않으므로 사용자 타입을 직접 사용하지 않는다는 점에 유의하십시오. 대신 HIR 타입 검사기에서 얻은 최종 inferred_type을 저장합니다. 그런 다음 MIR 타입 검사(typeck) 중에 그 영역(region)들을 새로운 NLL 추론 변수로 교체하고, 이를 실제 UserType과 연관시켜 올바른 영역 제약 조건을 다시 얻습니다.

MIR 타입 검사가 끝나면 모든 사용자 타입 표기는 더 이상 필요하지 않으므로 폐기됩니다.


  1. 리전(region) 또는 대여(loan)라고도 합니다. ↩