Rust 언어에서 메모리 관리하는 것은 전통적으로 저수준 프로그래밍에서 가장 복잡한 문제 중 하나로 여겨졌습니다. Rust가 등장하기 전 대부분의 언어는 메모리를 수동으로 관리해야 했고 (C/C++처럼) 이로 인해 메모리 누수 및 데이터 손상이 발생했습니다. Rust는 이 문제에 다른 접근 방식을 제시했으며, Vec<T>와 같은 컬렉션은 자동적이고 안전한 메모리 관리 전략을 사용하여 메모리 할당, 재배치(리사이즈) 및 해제를 소유권 및 차용 시스템을 통해 제어합니다.
문제는 대부분의 언어가 할당기 (GC)의 세부 사항을 너무 추상화하거나 프로그래머가 모든 것을 책임져야 한다는 점이었습니다 (malloc/free). 동적 배열의 경우 메모리 누수 및 배열의 경계를 초과하는 것을 주의 깊게 살펴봐야 하며 소유권을 침해하지 않아야 합니다.
해결책은 Rust에서 안전한 추상화를 통한 자동화입니다. Vec<T>는 힙에 메모리를 할당하고, 크기를 동적으로 증가시키며(보통 기하급수적으로 증가) 영역을 벗어나는 경우 자동으로 모든 것을 해제합니다 (RAII).
코드 예제:
fn main() { let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3); // 추가 시 크기 증가 및 메모리 재배치가 발생합니다. println!("Vector: {:?}", v); // main에서 벗어날 때 메모리가 자동으로 해제됩니다. }
주요 특징:
Vec<T>는 미리 메모리를 할당하고 필요할 때 재배치합니다.Vec에 요소를 추가할 때 배열의 증가 복잡도는 무엇인가요?
보통 push의 복잡도는 평균적으로 O(1)입니다. 그러나 배열이 가득 차면 새로운 메모리 영역이 할당되고(크기가 대략 두 배 증가), 모든 요소가 복사됩니다. 이 순간이 유일한 예외로 이 연산은 O(n)이 됩니다.
v[index]를 통해 범위를 벗어난 요소를 얻으려고 하면 어떤 일이 발생하나요?
대괄호를 사용할 경우 범위를 초과하면 패닉이 발생합니다. 안전하게 오류를 처리하려면 .get() 메서드를 사용해야 하며, 이 메서드는 Option을 반환합니다.
let element = v.get(10); // 인덱스가 없다면 None
벡터의 크기가 증가한 후 Vec 요소에 대한 참조를 사용할 수 있나요?
아니요, 벡터의 크기가 변경된 후(push로 가득 차서) 메모리가 이동할 수 있으며, 오래된 참조는 유효하지 않게 되어 컴파일 오류가 발생합니다 (또는 수동으로 사용하는 안전하지 않은 블록에서 정의되지 않은 동작이 발생할 수 있습니다).
개발자가 Vec<T>를 기반으로 메시지 캐시를 구현하고 요소에 대한 참조를 외부에 반환합니다. 새로운 요소가 추가되면 메모리 재배치가 발생하고 모든 기존 참조가 "dangling"이 되어 애플리케이션이 충돌합니다.
장점:
단점:
내부 식별 요소(인덱스/키 + 유효성 검사)를 사용하거나 단지 복사본/불변 값을 반환하여 Vec의 요소에 대한 장기 참조를 저장하지 않도록 합니다.
장점:
단점: