보편 영역
“보편 영역(universal regions)“은 코드에서 “이름 있는 라이프타임“을 가리킬 때 사용하는 이름입니다. 라이프타임 매개변수와 'static이 그 예입니다. 이 이름은 이러한 라이프타임이 “보편 양화(universally quantified)“되어 있다는 사실에서 유래합니다(즉, 해당 라이프타임의 모든 값에 대해 코드가 참임을 보장해야 합니다). 영역 추론 중에 라이프타임 매개변수가 어떻게 처리되는지 잠시 살펴볼 필요가 있습니다. 다음 예제를 살펴보십시오.
fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'b u32 {
x
}
이 예제는 일부러 컴파일되지 않게 만든 것입니다. &'a u32 타입인 x를 반환하지만, 시그니처는 &'b u32 값을 반환하겠다고 약속하기 때문입니다. 그렇다면 'a와 'b 같은 라이프타임은 영역 추론에 어떻게 통합되며, 이 오류는 어떻게 감지되는 것입니까?
보편 영역과 그 사이의 관계
영역 추론 초기에 가장 먼저 하는 일 중 하나는 UniversalRegions 구조체를 구성하는 것입니다. 이 구조체는 특정 함수의 스코프 안에 있는 여러 보편 영역을 추적합니다. 또한 UniversalRegionRelations 구조체도 생성하는데, 이 구조체는 보편 영역 사이의 관계를 추적합니다. 따라서 예를 들어 where 'a: 'b가 있다면, UniversalRegionRelations 구조체는 'a: 'b가 성립한다고 알려져 있음을 추적합니다(이는 outlives 함수로 검사할 수 있습니다).
모든 것은 영역 변수입니다
NLL 영역 추론이 작동하는 방식에서 중요한 점 하나는 모든 라이프타임이 번호가 매겨진 변수로 표현된다는 것입니다. 즉, 우리가 사용하는 region_kind::RegionKind의 변형은 ReVar 변형뿐입니다. 이러한 영역 변수는 인덱스에 따라 두 가지 주요 범주로 나뉩니다.
- 0..N: 보편 영역 – 여기서 논의하고 있는 영역입니다. 이 경우 코드는 선언된 관계를 만족하는 해당 변수의 어떠한 값에 대해서도 올발라야 합니다.
- N..M: 존재 영역 – 영역 추론기가 어떤 적절한 값을 찾아내야 하는 추론 변수입니다.
사실 보편 영역은 스코프에 들어온 위치에 따라 더 세분화할 수 있습니다(RegionClassification 타입을 참고하십시오). 이러한 세분화는 여기서 다루는 주제에서는 중요하지 않지만, 클로저 제약 전파를 고려할 때 중요해지므로 해당 부분에서 다룹니다.
영역 값의 원소로서의 보편 라이프타임
앞서 언급했듯이, 각 영역에 대해 추론하는 값은 집합 {E}입니다. 이 집합의 원소는 제어 흐름 그래프의 지점일 수도 있지만, 각 보편 라이프타임 'a에 대응하는 원소 end('a)일 수도 있습니다. 어떤 영역 R0의 값이 end('a)를 포함한다면, 이는 R0가 호출자에서 'a의 끝까지 이어져야 함을 의미합니다.
보편 영역의 “값”
영역 추론 중에는 다른 영역의 값을 계산하는 것과 같은 방식으로 각 보편 영역의 값을 계산합니다. 이 값은 사실상 해당 보편 영역의 하한, 즉 그 영역이 반드시 더 오래 살아야 하는 대상들을 나타냅니다. 이제 이 값을 사용하여 오류를 검사하는 방법을 설명합니다.
활성과 보편 영역
모든 보편 영역은 초기 활성 제약을 가지며, 이 제약은 함수 본문 전체를 포함합니다. 라이프타임 매개변수는 호출자에서 정의되므로, 이 특정 함수를 호출하는 함수 호출 전체를 포함해야 하기 때문입니다. 또한 각 보편 영역 'a는 자신의 활성 제약에 자기 자신(즉, end('a))을 포함합니다(즉, 'a는 자기 자신의 끝까지 이어져야 합니다). 코드에서 이러한 활성 제약은 init_free_and_bound_regions에서 설정됩니다.
보편 영역에 대한 outlives 제약 전파
그러면 이 절의 첫 번째 예제를 살펴보겠습니다.
fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'b u32 {
x
}
여기서 x를 반환하려면 &'a u32 <: &'b u32가 성립해야 하며, 이로부터 outlives 제약 'a: 'b가 생깁니다. 이를 기본 활성 제약과 결합하면 다음을 얻습니다.
'a live at {B, end('a)} // B represents the "function body"
'b live at {B, end('b)}
'a: 'b
따라서 'a: 'b 제약을 처리할 때 'a의 값에 end('b)를 추가하게 되며, 그 결과 최종 값은 {B, end('a), end('b)}가 됩니다.
오류 검출
제약 전파를 마친 후에는, 어떤 보편 영역 'a가 원소 end('b)를 포함한다면 'a: 'b가 함수의 바운드에 선언되어 있어야 한다는 제약을 강제합니다. 우리 예제처럼 선언되어 있지 않다면 이는 오류입니다. 이 검사는 check_universal_regions 함수에서 수행되며, 이 함수는 단순히 모든 보편 영역을 순회하면서 각 영역의 최종 값을 살펴보고 선언된 UniversalRegionRelations에 대해 테스트합니다.