THIR
THIR(“Typed High-Level Intermediate Representation”, 타입이 지정된 고수준 중간 표현)는 이전에 “High-Level Abstract IR“의 약자인 HAIR로 불렸으며, 타입 검사 이후에 생성되는, rustc가 사용하는 또 다른 IR입니다. 이것은 ( 2024년 1월 기준) MIR 구성, 완전성 검사, unsafe 검사에 사용됩니다.
이름에서 짐작할 수 있듯이, THIR는 모든 타입이 채워진 HIR의 하위 변환(lowered) 버전이며, 이는 타입 검사가 완료된 후에 가능합니다. 하지만 THIR에는 HIR과 구별되는 몇 가지 흥미로운 기능이 더 있습니다:
-
MIR와 마찬가지로 THIR는 본문, 즉 “실행 가능한 코드“만을 표현합니다. 여기에는 함수 본문뿐만 아니라, 예를 들어
const초기화 식도 포함됩니다. 구체적으로는 모든 본문 소유자에 대해 THIR가 생성됩니다. 따라서 THIR에는struct나trait같은 아이템에 대한 표현이 없습니다. -
THIR의 각 본문은 컴파일 과정이 끝날 때까지 저장되는 것(HIR는 이렇게 저장됩니다)과 달리, 일시적으로만 저장되며 더 이상 필요하지 않게 되는 즉시 해제됩니다.
-
THIR는 모든 노드의 타입을 제공할 뿐만 아니라, HIR에 비해 추가적인 디슈가링(desugaring)도 거칩니다. 예를 들어 자동 참조와 역참조가 명시적으로 표현되며, 메서드 호출과 오버로드된 연산자는 일반 함수 호출로 변환됩니다. 소멸 스코프(destruction scope) 또한 명시적으로 표현됩니다.
-
문(statement), 표현식, match 갈래, 블록, 매개변수는 각각 따로 저장됩니다. 예를 들어
stmts배열에 있는 문은exprs배열 내 표현식을 그 인덱스(ExprId로 표현됨)로 참조합니다.
THIR는 rustc_mir_build::thir에 있습니다. thir::Expr를 구성하려면 THIR가 할당될 메모리 아레나를 전달하여 thir_body 함수를 사용하면 됩니다. 이 아레나를 해제하면 THIR도 소멸되며, 이는 최대 메모리 사용량을 억제하는 데 유용합니다. 크레이트의 모든 본문에 대한 THIR 표현을 동시에 메모리에 두는 것은 매우 부담이 클 것입니다.
rustc에 -Zunpretty=thir-flat 플래그를 전달하면 THIR의 디버그 표현을 얻을 수 있습니다.
예시로 다음 코드를 사용해 봅시다:
fn main() {
let x = 1 + 2;
}
THIR에서 이것이 표현되는 방식은 다음과 같습니다( 2026년 7월 기준):
#![allow(unused)]
fn main() {
DefId(0:3 ~ main[26fd]::main):
Thir {
body_type: Fn(
fn(),
),
attributes: {},
// no match arms
arms: [],
blocks: [
Block {
targeted_by_break: false,
region_scope: Node(1),
span: main.rs:1:11: 3:2 (#0),
stmts: [
s0,
],
expr: None,
safety_mode: Safe,
},
],
exprs: [
// expression 0, a literal with a value of 1
Expr {
kind: Literal {
lit: Spanned {
node: Int(
Pu128(
1,
),
Unsuffixed,
),
span: main.rs:2:13: 2:14 (#0),
},
neg: false,
},
ty: i32,
temp_scope_id: 4,
span: main.rs:2:13: 2:14 (#0),
},
// expression 1, scope surrounding literal 1
Expr {
kind: Scope {
region_scope: Node(4),
hir_id: HirId(DefId(0:3 ~ main[26fd]::main).4),
// reference to expression 0 above
value: e0,
},
ty: i32,
temp_scope_id: 4,
span: main.rs:2:13: 2:14 (#0),
},
// expression 2, literal 2
Expr {
kind: Literal {
lit: Spanned {
node: Int(
Pu128(
2,
),
Unsuffixed,
),
span: main.rs:2:17: 2:18 (#0),
},
neg: false,
},
ty: i32,
temp_scope_id: 5,
span: main.rs:2:17: 2:18 (#0),
},
// expression 3, scope surrounding literal 2
Expr {
kind: Scope {
region_scope: Node(5),
hir_id: HirId(DefId(0:3 ~ main[26fd]::main).5),
// reference to expression 2 above
value: e2,
},
ty: i32,
temp_scope_id: 5,
span: main.rs:2:17: 2:18 (#0),
},
// expression 4, represents 1 + 2
Expr {
kind: Binary {
op: Add,
// references to scopes surrounding literals above
lhs: e1,
rhs: e3,
},
ty: i32,
temp_scope_id: 3,
span: main.rs:2:13: 2:18 (#0),
},
// expression 5, scope surrounding expression 4
Expr {
kind: Scope {
region_scope: Node(3),
hir_id: HirId(DefId(0:3 ~ main[26fd]::main).3),
value: e4,
},
ty: i32,
temp_scope_id: 3,
span: main.rs:2:13: 2:18 (#0),
},
// expression 6, block around statement
Expr {
kind: Block {
block: b0,
},
ty: (),
temp_scope_id: 8,
span: main.rs:1:11: 3:2 (#0),
},
// expression 7, scope around block in expression 6
Expr {
kind: Scope {
region_scope: Node(8),
hir_id: HirId(DefId(0:3 ~ main[26fd]::main).8),
value: e6,
},
ty: (),
temp_scope_id: 8,
span: main.rs:1:11: 3:2 (#0),
},
],
stmts: [
Stmt {
kind: Let {
remainder_scope: Remainder { block: 1, first_statement_index: 0},
init_scope: Node(2),
pattern: Pat {
ty: i32,
span: main.rs:2:9: 2:10 (#0),
extra: None,
kind: Binding {
name: "x",
mode: BindingMode(
No,
Not,
),
var: LocalVarId(
HirId(DefId(0:3 ~ main[26fd]::main).7),
),
ty: i32,
subpattern: None,
is_primary: true,
is_shorthand: false,
},
},
initializer: Some(
e5,
),
else_block: None,
hir_id: HirId(DefId(0:3 ~ main[26fd]::main).6),
span: main.rs:2:5: 2:18 (#0),
},
},
],
params: [],
}
}