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

HIR

HIR(“High-Level Intermediate Representation”, 고수준 중간 표현)은 rustc 대부분에서 사용되는 주요 IR입니다. 이는 파싱, 매크로 확장, 이름 해석 이후에 생성되는 추상 구문 트리(AST)를 컴파일러가 다루기 쉬운 형태로 표현한 것입니다(HIR이 생성되는 방식은 로워링을 참고하십시오). HIR의 많은 부분은 Rust 표면 구문과 상당히 비슷합니다. 다만 Rust의 일부 표현식 형태는 디슈가링(desugaring)되어 사라집니다. 예를 들어 for 루프는 loop로 변환되므로 HIR에 나타나지 않습니다. 이 때문에 HIR은 일반적인 AST보다 분석하기에 더 적합합니다.

이 장에서는 HIR의 주요 개념을 다룹니다.

rustc에 -Z unpretty=hir-tree 플래그를 전달하면 코드의 HIR 표현을 볼 수 있습니다:

cargo rustc -- -Z unpretty=hir-tree

-Z unpretty=hir 옵션을 사용하면 원래 소스 코드 표현식에 더 가까운 HIR을 생성할 수도 있습니다:

cargo rustc -- -Z unpretty=hir

대역 외 저장소(out-of-band storage)와 Crate 타입

HIR의 최상위 자료 구조는 Crate이며, 현재 컴파일 중인 크레이트의 내용을 저장합니다(HIR은 항상 현재 크레이트에 대해서만 구성합니다). AST의 크레이트 자료 구조는 기본적으로 루트 모듈만 담고 있지만, HIR의 Crate 구조체는 크레이트의 내용을 정리하여 더 쉽게 접근할 수 있도록 하는 여러 맵과 그 밖의 요소를 담고 있습니다.

예를 들어 HIR에서는 개별 아이템(예: 모듈, 함수, 트레이트, impl 등)의 내용에 부모에서 바로 접근할 수 없습니다. 예를 들어 함수 bar()를 포함하는 모듈 아이템 foo가 있다고 합시다:

#![allow(unused)]
fn main() {
mod foo {
    fn bar() { }
}
}

그러면 HIR에서 모듈 foo의 표현(Mod 구조체)은 bar()의 ItemId I만 가지게 됩니다. 함수 bar()의 세부 정보를 얻으려면 items 맵에서 I를 조회합니다.

이 표현 방식의 좋은 점 하나는 HIR 전체를 샅샅이 뒤질 필요 없이 이 맵들의 키-값 쌍을 순회하는 것만으로 크레이트의 모든 아이템을 순회할 수 있다는 것입니다. 트레이트 아이템과 impl 아이템, 그리고 “바디(body)”(아래에서 설명합니다)에 대해서도 비슷한 맵이 있습니다.

표현을 이렇게 구성하는 또 다른 이유는 증분 컴파일과 더 잘 통합하기 위해서입니다. 이렇게 하면 &rustc_hir::Item(예: 모듈 foo에 대한 것)에 접근하더라도 함수 bar()의 내용에는 바로 접근할 수 없습니다. 대신 bar()의 id에만 접근할 수 있으며, bar()의 내용을 조회하려면 그 id를 넘겨 어떤 함수를 호출해야 합니다. 이를 통해 컴파일러는 여러분이 bar()의 데이터에 접근했다는 사실을 관찰하고 그 의존성을 기록할 수 있습니다.

HIR의 식별자

HIR은 함께 존재하면서 각기 다른 목적을 수행하는 여러 종류의 식별자를 사용합니다.

  • DefId는 이름에서 알 수 있듯이 주어진 크레이트 안의 특정 정의, 즉 최상위 아이템을 식별합니다. 이는 두 부분으로 구성됩니다. 하나는 정의가 유래한 크레이트를 식별하는 CrateNum이고, 다른 하나는 크레이트 내에서 정의를 식별하는 DefIndex입니다. HirId와 달리 DefId는 모든 표현식마다 있는 것이 아니므로 여러 컴파일에 걸쳐 더 안정적입니다.

  • LocalDefId는 기본적으로 현재 크레이트에서 유래한 것으로 알려진 DefId입니다. 따라서 CrateNum 부분을 생략할 수 있으며, 로컬 정의를 기대하는 함수에 로컬 정의만 전달되도록 타입 시스템으로 보장할 수 있습니다.

  • HirId는 현재 크레이트의 HIR에 있는 노드를 고유하게 식별합니다. 이는 두 부분으로 구성됩니다. 하나는 owner이고, 다른 하나는 owner 내에서 고유한 local_id입니다. 이 조합을 사용하면 값이 더 안정적이 되며, 이는 증분 컴파일에 도움이 됩니다. DefId와 달리 HirId는 표현식과 같은 세분화된 엔티티를 가리킬 수 있지만, 현재 크레이트 안에서만 유효합니다.

  • BodyId는 현재 크레이트의 HIR Body를 식별합니다. 현재는 HirId를 감싸는 래퍼에 불과합니다. HIR 본문에 대한 자세한 내용은 HIR 장을 참고하십시오.

이러한 식별자들은 TyCtxt를 통해 서로 변환할 수 있습니다.

HIR 연산

HIR을 다룰 때는 대부분 TyCtxt를 통해 작업하게 됩니다. 여기에는 대부분 hir_ 접두사가 붙은 여러 메서드가 hir::map 모듈에 정의되어 있으며, 이 메서드들은 다양한 종류의 ID를 서로 변환하고 HIR 노드와 연관된 데이터를 조회하는 데 사용됩니다.

예를 들어 LocalDefId가 있고 이를 HirId로 변환하려면 tcx.local_def_id_to_hir_id(def_id)를 사용하면 됩니다. 로컬 아이템만 HIR 노드를 가지므로 DefId가 아니라 LocalDefId가 필요합니다.

마찬가지로 tcx.hir_node(n)를 사용하면 HirId에 해당하는 노드를 조회할 수 있습니다. 이 메서드는 Option<Node<'hir>>를 반환하며, 여기서 Node는 맵에 정의된 열거형입니다. 이 값을 패턴 매칭하면 HirId가 어떤 종류의 노드를 가리켰는지 알 수 있고, 데이터 자체에 대한 포인터도 얻을 수 있습니다. n이 어떤 종류의 노드인지 미리 아는 경우도 많습니다. 예를 들어 n이 반드시 어떤 HIR 표현식이라는 것을 안다면 tcx.hir_expect_expr(n)를 사용할 수 있습니다. 이 메서드는 &hir::Expr를 추출하여 반환하며, n이 실제로 표현식이 아니면 패닉을 일으킵니다.

마지막으로 tcx.parent_hir_node(n)와 같은 호출로 노드의 부모를 찾을 수 있습니다.

HIR 본문

rustc_hir::Body는 함수나 클로저의 본문, 상수의 정의처럼 실행 가능한 코드를 나타냅니다. 본문은 소유자와 연관됩니다. 소유자는 보통 아이템(예: fn(), const)이지만, 클로저 표현식(예: |x, y| x + y)일 수도 있습니다. TyCtxt를 사용하면 주어진 def-id에 연관된 본문을 찾거나(hir_maybe_body_owned_by) 본문의 소유자를 찾을 수 있습니다(hir_body_owner_def_id).