본문 바로가기

언어/Rust

[ Rust ] 반복자 무효화(iterator invalidation)에 대해서 알아보기

반응형

반복자 무효화(Iterator Invalidations)는 데이터 구조를 순회하는 동안, 그 데이터 구조가 변경될 때 발생하는 문제입니다. 일반적으로 배열, 벡터, 링크드 리스트 같은 자료 구조에서 반복자를 사용해 요소들을 순회하는 도중에 해당 자료 구조에 요소를 추가하거나 제거하면, 반복자가 가리키는 위치가 잘못되어 예상치 못한 동작을 초래할 수 있습니다. 이 상황을 반복자 무효화라고 합니다.

반복자 무효화의 예시 (C++)

C++에서 벡터(std::vector)를 사용할 때, 반복자 무효화가 어떻게 발생할 수 있는지 살펴보겠습니다.

#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    auto it = numbers.begin();

    numbers.push_back(6);  // 벡터의 크기가 변하면서 반복자가 무효화됨

    std::cout << *it << std::endl;  // 정의되지 않은 동작 발생 가능
    return 0;
}

위 코드에서 numbers.push_back(6)은 벡터의 크기를 늘려 메모리 재할당을 유발할 수 있으며, 이로 인해 it 반복자가 무효화됩니다. 이후 이 무효화된 반복자를 사용하려고 하면 프로그램이 예기치 않게 동작하거나 충돌할 수 있습니다.

Rust에서의 반복자 무효화 방지

Rust는 이러한 반복자 무효화를 방지하기 위해 매우 엄격한 메모리 및 소유권 규칙을 따릅니다. Rust의 반복자는 기본적으로 안전하게 설계되어 있으며, 데이터를 순회하는 동안 해당 데이터 구조에 대한 변경을 시도하면 컴파일 타임에 오류가 발생하도록 되어 있습니다.

예제: Rust에서 안전한 반복자 사용

fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];
    let iter = numbers.iter();  // 불변 참조로 반복자 생성

    numbers.push(6);  // 벡터에 요소 추가 (이 시점에서 컴파일 에러 발생)

    for number in iter {
        println!("{}", number);
    }
}

이 코드에서는 numbers.iter()로 반복자를 생성한 후에 numbers.push(6)를 호출하려고 시도합니다. 하지만 Rust에서는 불변 참조(&)를 기반으로 반복자를 생성한 상태에서 가변 참조(&mut)로 벡터를 수정하려고 하면 컴파일 타임에 오류가 발생합니다. 이는 Rust가 반복자 무효화를 원천적으로 방지하는 방식입니다.

Rust의 안전성 모델

Rust의 소유권과 대여(borrowing) 시스템은 데이터 구조를 순회하는 동안 구조 자체가 변경되지 않도록 보장합니다. 불변 참조가 활성화된 상태에서는 해당 데이터 구조를 변경할 수 없고, 가변 참조가 존재할 경우 반복자를 생성할 수 없습니다. 이를 통해 반복자 무효화로 인한 런타임 오류를 원천적으로 차단할 수 있습니다.

이처럼 Rust는 반복자 무효화를 방지하여 보다 안전하고 예측 가능한 코드를 작성할 수 있도록 도와줍니다.

반응형