2단계 빌림
2단계 빌림은 가변 빌림보다 더 관대한 형태로, vec.push(vec.len())와 같은 중첩된 메서드 호출을 허용합니다. 이러한 빌림은 처음에는 “예약” 단계에서 공유 빌림으로 동작하다가, 이후에 완전한 가변 빌림으로 “활성화“될 수 있습니다.
특정한 암시적 가변 빌림만 2단계 빌림이 될 수 있으며, 소스 코드에 있는 &mut이나 ref mut은 결코 2단계 빌림이 되지 않습니다. 2단계 빌림을 생성하는 경우는 다음과 같습니다:
- 가변 참조 리시버를 받는 메서드를 호출할 때의 자동 참조(autoref) 빌림.
- 함수 인자에서의 가변 재빌림(reborrow).
- 오버로드된 복합 대입 연산자에서의 암시적 가변 빌림.
몇 가지 예를 들면 다음과 같습니다:
#![allow(unused)]
fn main() {
// In the source code
// Case 1:
let mut v = Vec::new();
v.push(v.len());
let r = &mut Vec::new();
r.push(r.len());
// Case 2:
std::mem::replace(r, vec![1, r.len()]);
// Case 3:
let mut x = std::num::Wrapping(2);
x += x;
}
2단계 빌림이 드러나도록 이를 충분히 전개하면 다음과 같습니다:
// Case 1:
let mut v = Vec::new();
let temp1 = &two_phase v;
let temp2 = v.len();
Vec::push(temp1, temp2);
let r = &mut Vec::new();
let temp3 = &two_phase *r;
let temp4 = r.len();
Vec::push(temp3, temp4);
// Case 2:
let temp5 = &two_phase *r;
let temp6 = vec![1, r.len()];
std::mem::replace(temp5, temp6);
// Case 3:
let mut x = std::num::Wrapping(2);
let temp7 = &two_phase x;
let temp8 = x;
std::ops::AddAssign::add_assign(temp7, temp8);
빌림이 2단계가 될 수 있는지는 타입 검사 이후 AutoBorrow의 플래그로 추적되며, 이 플래그는 MIR 구성 중에 BorrowKind로 변환됩니다.
각 2단계 빌림은 한 번만 사용되는 임시 변수에 대입됩니다. 따라서 다음과 같이 정의할 수 있습니다:
- 임시 변수에 대입되는 지점을 2단계 빌림의 예약 지점이라고 합니다.
- 임시 변수가 사용되는 지점은 사실상 항상 함수 호출이며, 이 지점을 활성화 지점이라고 합니다.
활성화 지점은 GatherBorrows 방문자를 사용하여 찾습니다. 그런 다음 BorrowData가 해당 빌림의 예약 지점과 활성화 지점을 모두 보관합니다.
2단계 빌림 검사하기
2단계 빌림은 다음과 같은 예외를 제외하면 가변 빌림인 것처럼 취급됩니다:
- MIR의 모든 위치에서 해당 위치에서 활성화되는 2단계 빌림이 있는지 검사합니다. 살아 있는 2단계 빌림이 어떤 위치에서 활성화되면, 그 2단계 빌림과 충돌하는 빌림이 없는지 검사합니다.
- 예약 지점에서는 충돌하는 살아 있는 가변 빌림이 있으면 오류를 발생시킵니다. 그리고 충돌하는 공유 빌림이 있으면 린트를 발생시킵니다.
- 예약 지점과 활성화 지점 사이에서 2단계 빌림은 공유 빌림으로 동작합니다. 현재 그러한 지점에 있는지는 (
is_active에서) MIR 그래프의Dominators를 사용하여 판단합니다. - 활성화 지점 이후에는 2단계 빌림이 가변 빌림으로 동작합니다.