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

이동 경로

실제로는 지역 변수 단위로 초기화를 추적하는 것만으로는 충분하지 않습니다. Rust에서는 필드 단위로도 이동과 초기화를 수행할 수 있습니다:

fn foo() {
    let a: (Vec<u32>, Vec<u32>) = (vec![22], vec![44]);

    // a.0 and a.1 are both initialized

    let b = a.0; // moves a.0

    // a.0 is not initialized, but a.1 still is

    let c = a.0; // ERROR
    let d = a.1; // OK
}

이를 처리하기 위해 우리는 이동 경로(move path) 단위로 초기화를 추적합니다. MovePath는 사용자가 초기화하거나 이동하는 등의 작업을 할 수 있는 어떤 위치를 나타냅니다. 예를 들어 지역 변수 a를 나타내는 이동 경로가 있고, a.0을 나타내는 이동 경로도 있습니다. 이동 경로는 MIR의 Place 개념에 대략 대응하지만, 이동 분석을 더 효율적으로 수행할 수 있는 방식으로 인덱싱됩니다.

이동 경로 인덱스

MovePath 자료 구조가 존재하기는 하지만, 이를 직접 참조하는 일은 없습니다. 대신 모든 코드는 MovePathIndex 타입의 인덱스를 주고받습니다. 이동 경로에 대한 정보가 필요하다면 이 인덱스를 MoveData의 move_paths 필드와 함께 사용합니다. 예를 들어 MovePathIndex mpi를 MIR Place로 변환하려면 다음과 같이 MovePath::place 필드에 접근할 수 있습니다:

move_data.move_paths[mpi].place

이동 경로 구성하기

MIR 빌림 검사에서 가장 먼저 하는 일 중 하나는 이동 경로의 집합을 구성하는 것입니다. 이 작업은 MoveData::gather_moves 함수의 일부로 수행됩니다. 이 함수는 MoveDataBuilder라는 MIR 방문자(visitor)를 사용하여 MIR을 순회하면서 그 안의 각 Place가 어떻게 접근되는지 살펴봅니다. 그러한 각 Place에 대해 대응하는 MovePathIndex를 구성합니다. 또한 해당 이동 경로가 언제/어디서 이동/초기화되는지도 기록하지만, 이에 대해서는 이후 절에서 다루겠습니다.

허용되지 않는 이동 경로

실제로는 사용되는 모든 Place에 대해 이동 경로를 만들지는 않습니다. 특히 어떤 Place에서 이동하는 것이 허용되지 않는다면 MovePathIndex가 필요하지 않습니다. 몇 가지 예는 다음과 같습니다:

  • 배열의 개별 요소는 이동할 수 없으므로, 예를 들어 foo: [String; 3]이 있다면 foo[1]에 대한 이동 경로는 존재하지 않습니다.
  • 빌린 참조의 내부에서는 이동할 수 없으므로, 예를 들어 foo: &String이 있다면 *foo에 대한 이동 경로는 존재하지 않습니다.

이러한 규칙은 Place를 MovePathIndex로 변환하는 move_path_for 함수가 강제합니다. 방금 살펴본 것과 같은 오류 상황에서 이 함수는 Err를 반환합니다. 이는 결과적으로 해당 위치들이 초기화되었는지 굳이 추적할 필요가 없다는 뜻입니다(이로써 오버헤드가 줄어듭니다).

프로젝션

이동 경로의 프로젝션은 PlaceElem을 사용하는 대신 MoveSubPath로 저장됩니다. 밖으로 이동할 수 없는 프로젝션과 건너뛸 수 있는 프로젝션은 표현되지 않습니다.

(슬라이스 패턴으로 생성되는) 배열의 부분 슬라이스 프로젝션은 특별합니다. 이는 부분 슬라이스의 각 요소마다 하나씩, 여러 개의 ConstantIndex 하위 경로로 변환됩니다.

이동 경로 조회하기

Place가 있고 이를 MovePathIndex로 변환하고 싶다면, MoveData의 rev_lookup 필드에 있는 MovePathLookup 구조체를 사용하여 변환할 수 있습니다. 두 가지 메서드가 있습니다:

  • find_local: 지역 변수를 나타내는 mir::Local을 인자로 받습니다. 모든 지역 변수에 대해 MovePathIndex를 항상 생성하므로 이쪽이 더 쉬운 메서드입니다.
  • find: 임의의 Place를 인자로 받습니다. 이 메서드는 사용하기가 조금 더 번거로운데, 바로 앞의 “불법 이동 경로” 절에서 설명했듯이 모든 Place에 대해 MovePathIndex가 있는 것은 아니기 때문입니다. 따라서 find는 찾을 수 있었던 경로 중 실제로 존재하는 가장 가까운 경로를 나타내는 LookupResult를 반환합니다(예를 들어 foo[1]에 대해서는 foo의 경로만 반환할 수도 있습니다).

상호 참조

앞서 언급했듯이 이동 경로는 큰 벡터에 저장되며 각자의 MovePathIndex를 통해 참조됩니다. 그러나 이 벡터 안에서 이동 경로는 트리 구조로도 구성되어 있습니다. 예를 들어 a.b.c에 대한 MovePathIndex가 있으면 그 부모 이동 경로인 a.b로 이동할 수 있습니다. 모든 자식 경로를 순회할 수도 있습니다. 예를 들어 a.b에서 순회하여 a.b.c 경로를 찾을 수 있습니다(이때 순회하는 대상은 참조될 수 있었던 가능한 모든 경로가 아니라 소스에서 실제로 참조된 경로뿐입니다). 이러한 참조는 예를 들어 find_in_move_path_or_its_descendants 함수에서 사용됩니다. 이 함수는 어떤 이동 경로(예: a.b) 또는 그 이동 경로의 자식(예: a.b.c)이 주어진 술어를 만족하는지 판단합니다.