# 벡터(Vector)

Vec는 러스트 표준 라이브러리에서 제공하는 동적 배열로서, 동적으로 크기를 조절할 수 있는 컬렉션이다. 힙 영역에 데이터를 저장하며, 러스트의 소유권 규칙을 따르면서도 편리한 메모리 관리 기능을 제공한다. 일반적으로 Vec는 같은 자료형 T를 연속적으로 저장해야 할 때 사용된다. 메모리 상에서 연속적으로 할당되므로, 요소에 빠르게 접근하거나 순회하는 데 유리하다. 단, 중간에 요소를 삽입하거나 삭제해야 하는 상황이라면, 연속적인 메모리 재배치를 유발할 수 있으므로 다른 컬렉션(예: LinkedList)을 고려해야 할 수도 있다.

Vec를 생성하는 가장 간단한 방법은 vec! 매크로를 사용하는 것이다. 초기 요소를 명시하기 위해서는 아래와 같은 방식을 사용할 수 있다.

```
fn main() {
    let v = vec![1, 2, 3];
    println!("{:?}", v);
}
```

만약 초기값 없이 빈 벡터를 선언하고 싶다면 Vec::new()를 사용할 수 있다. 이때 타입을 명시하지 않으면 러스트 컴파일러가 제네릭 타입 매개변수 T를 추론할 수 없는 경우가 발생할 수 있으므로, 다음과 같이 구체적인 타입 정보를 제공하거나 이후에 push 등을 통해 타입이 정해질 수 있도록 코드를 작성한다.

```
fn main() {
    let mut v: Vec<i32> = Vec::new();
    v.push(10);
    v.push(20);
    println!("{:?}", v);
}
```

Vec는 동적으로 크기를 조절할 수 있다. push 메서드를 통해 새로운 요소를 끝에 추가할 수 있으며, pop 메서드로 벡터의 마지막 요소를 제거하고 반환받을 수 있다. 이 과정에서 벡터가 자신이 현재 수용할 수 있는 요소 수(capacity)를 초과하는 push 요청을 받으면, 더 큰 힙 공간을 재할당한 뒤 기존 내용을 그쪽으로 복사한다. 이 같은 내부 동작으로 인해 push가 완전히 상수 시간이 되지는 않지만, 충분히 큰 capacity를 미리 확보해두면 amortized 상수 시간으로 동작할 수 있다.

벡터의 요소에 접근하는 방식에는 인덱스 연산자(\[])와 get 메서드가 있다. 인덱스 연산자를 통해 접근할 경우 범위를 벗어난 인덱스를 사용하면 런타임 패닉을 일으킨다. 이는 프로그램을 곧바로 중단시킬 만큼 치명적인 오류 상황으로 간주되기 때문이다. 반면 get 메서드는 Option<\&T>를 반환하므로, 벡터 범위를 넘어서는 인덱스를 요청할 때 None을 돌려준다. 코드 예시는 다음과 같다.

```
fn main() {
    let v = vec![10, 20, 30];
    println!("{}", v[1]);                // 정수 20을 출력
    // println!("{}", v[3]);            // 패닉 발생
    match v.get(3) {
        Some(value) => println!("{}", value),
        None => println!("해당 인덱스 없음"),
    }
}
```

벡터에서 요소를 읽을 때는 불변 참조를 사용하고, 변경할 때는 가변 참조를 사용한다. 러스트의 빌림 규칙에 따라 하나의 가변 참조 또는 여러 개의 불변 참조만 동시에 가질 수 있다. 다른 컬렉션과 마찬가지로, Vec 안에 있는 데이터를 순회할 때는 이러한 빌림 규칙을 만족해야 한다. 가령 전체 벡터를 반복 처리하면서 내부 요소를 수정하고 싶다면, 가변 참조를 하나만 소유한 반복자를 통해 조작해야 한다. 예를 들어 다음 코드를 보자.

```
fn main() {
    let mut v = vec![1, 2, 3];
    for x in &mut v {
        *x *= 10;
    }
    println!("{:?}", v);
}
```

위 코드는 벡터 v에 대한 가변 참조 \&mut v를 통해 반복자를 생성했으므로, 순회 중에는 다른 어떤 곳에서도 v를 변경하거나 불변 참조로 빌릴 수 없다. 이처럼 러스트의 소유권과 빌림 모델은 런타임 에러를 줄이는 데 도움을 준다.

벡터는 용량(capacity)이라는 내부 값을 갖고 있으며, 이는 벡터가 재할당 없이 저장할 수 있는 최대 요소 수를 나타낸다. 예를 들어 빈 벡터를 만든 뒤 push를 수행하면, 어떤 시점에서 내부적으로 재할당을 일으켜 용량을 늘린다. 필요하다면 reserve나 reserve\_exact 메서드를 통해 미리 용량을 확보할 수도 있다. 이러한 사전 확보는 재할당 연산을 회피하여 성능 이점을 얻을 수 있게 해준다. capacity를 줄이려면 shrink\_to\_fit 같은 메서드를 사용하면 된다. 물론 모든 재할당이 자동으로 처리되므로, 일반적인 상황에서는 굳이 이 메서드들을 사용할 필요가 없지만, 높은 성능이 요구되는 상황에서는 세부 조정이 필요할 수 있다.

벡터는 Stack 영역에 소량의 구조체 메타데이터(예: capacity, length, 힙 포인터)만을 유지하고, 실제 데이터는 Heap 영역에 연속적으로 저장한다. 이러한 구조는 대용량 데이터를 처리할 때 스택 메모리를 과도하게 사용하지 않도록 하고, 새로운 요소를 추가할 때 필요하면 힙을 재할당함으로써 유연하게 확장이 가능하다는 장점이 있다. 그러나 중간에 요소를 삽입하거나 제거하려면, 연속적인 메모리 구조를 다시 정렬해야 하므로 많은 요소에 대한 복사가 필요할 수 있다. 이 때문에 부분적으로 삽입이나 삭제가 빈번히 발생한다면, 다른 자료구조를 선택하는 편이 나을 수 있다.

러스트에서 벡터는 여러 스레드가 동시에 접근하도록 설계되지 않았다. 다중 스레드 환경에서 안전한 공유가 필요하다면, Arc\<Mutex> 같은 스마트 포인터와 동기화 프리미티브를 함께 사용하는 방식을 고려해야 한다. 이때에도 러스트의 소유권과 빌림 규칙에 의해 자료 경쟁(race condition)과 같은 문제가 발생하지 않도록 컴파일 타임에 점검할 수 있다.

마지막으로 벡터가 더 이상 필요 없어 범위 밖으로 벗어나는 순간, 러스트는 자동으로 drop을 호출하여 힙에 할당된 메모리를 해제한다. 이는 스코프 기반의 메모리 관리 방식 덕분에 가능하며, 사용자는 메모리 해제 작업을 명시적으로 호출할 필요가 없다. 다른 모든 컬렉션도 마찬가지 원리를 따르지만, 벡터가 특히 자주 쓰이므로 메모리 해제 타이밍을 정확히 이해해두는 것이 유용하다.

Vec는 이처럼 러스트에서 가장 널리 사용되는 컬렉션 중 하나이며, 빠른 인덱싱 접근과 손쉬운 순회를 제공함과 동시에 메모리 안전성을 보장한다. 대용량 데이터를 다루고, 동적으로 크기를 조절해야 하며, 연속적인 저장 방식이 유리한 상황이라면 벡터를 우선적으로 고려하면 된다. Rust 프로그래밍에서 벡터를 적절히 다룰 수 있다면, 안전성과 성능을 모두 잡는 효율적인 코드를 작성하는 데 큰 도움을 받을 수 있을 것이다.
