콘텐츠로 이동

Rust 벡터

벡터(Vec)는 배열과 비슷하지만, 실행 중에 늘어나거나 줄어들 수 있어요. 일상적인 Rust 코드에서 무언가의 목록이 필요할 때는 크기가 고정된 배열보다 Vec을 훨씬 더 자주 쓰게 될 거예요.

fn main() {
let numbers: Vec<i32> = vec![1, 2, 3];
println!("{:?}", numbers);
}

vec![]는 값의 목록으로 Vec을 만드는 매크로예요(!가 붙어 있죠). 배열 리터럴을 쓰는 방식과 비슷해요.

▶ 직접 실행해 보기

빈 벡터로 시작해서 하나씩 채워 나갈 수도 있어요.

let mut numbers: Vec<i32> = Vec::new();

끝에 추가하려면 .push()를 쓰세요. 벡터를 바꾸는 거니까 벡터가 mut여야 해요.

fn main() {
let mut numbers = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
println!("{:?}", numbers);
}

실행 결과:

[1, 2, 3]
▶ 직접 실행해 보기

배열과 똑같이 대괄호 문법을 써요.

fn main() {
let fruits = vec!["사과", "바나나", "체리"];
println!("{}", fruits[0]);
println!("길이: {}", fruits.len());
}
▶ 직접 실행해 보기

잘못된 인덱스에서도 패닉이 일어나지 않는 더 안전한 방법을 원한다면 .get()을 쓰세요. 대신 Option을 돌려줘요(자세한 내용은 Option 페이지에서 다뤄요).

fn main() {
let fruits = vec!["사과", "바나나", "체리"];
match fruits.get(10) {
Some(fruit) => println!("찾았어요: {fruit}"),
None => println!("그 인덱스에는 과일이 없어요"),
}
}
▶ 직접 실행해 보기
fn main() {
let mut numbers = vec![1, 2, 3, 4, 5];
numbers.pop(); // 마지막 항목을 삭제해요 -> [1, 2, 3, 4]
numbers.remove(0); // 인덱스로 삭제해요 -> [2, 3, 4]
println!("{:?}", numbers);
}
▶ 직접 실행해 보기
fn main() {
let numbers = vec![10, 20, 30];
for n in &numbers {
println!("{n}");
}
}
▶ 직접 실행해 보기

numbers 앞의 &를 눈여겨보세요. for n in &numbers로 순회하면 벡터의 소유권을 가져가는 대신 각 항목을 대여(borrow)해요. &를 빼면 벡터가 반복문 안으로 이동(move)해서, 그 뒤로는 numbers를 쓸 수 없어요. 거의 언제나 원하는 건 대여하는 쪽이에요.

타입이 Vec<i32>라고 적혀 있는 걸 보셨을 거예요. <i32> 부분은 “i32 값의 벡터”라는 뜻이에요. 이렇게 꺾쇠괄호를 쓰는 표기를 제네릭(generic)이라고 하고, 제네릭 페이지에서 제대로 다시 만나요. 지금은 Vec<T>를 “T 타입의 값을 담는 벡터”라고 읽으면 충분해요.

빈 벡터로 시작하세요. 1부터 10까지 돌면서 짝수만 넣으세요. 그다음 벡터와 항목 개수를 출력해 보세요. 힌트: n % 2 == 0이면 숫자 n은 짝수예요.

정답 보기
fn main() {
let mut evens = Vec::new();
for n in 1..=10 {
if n % 2 == 0 {
evens.push(n);
}
}
println!("{:?}", evens);
println!("짝수를 {}개 찾았어요", evens.len());
}

실행 결과:

[2, 4, 6, 8, 10]
짝수를 5개 찾았어요
▶ 직접 실행해 보기

타입을 한 번도 적지 않았다는 점에 주목하세요. Rust는 push(n) 호출을 보고 evens가 Vec<i32>라는 걸 알아내요.

벡터에 접시 세 개를 차례로 넣으세요: "파랑", "초록", 그리고 "빨강". .pop()으로 맨 위의 접시 두 개를 꺼내면서 하나씩 출력하세요. 마지막으로 남은 것을 출력해 보세요. 힌트: .pop()은 Option을 돌려주니까 {:?}로 출력하세요.

정답 보기
fn main() {
let mut plates = Vec::new();
plates.push("파랑");
plates.push("초록");
plates.push("빨강");
println!("꺼낸 접시: {:?}", plates.pop());
println!("꺼낸 접시: {:?}", plates.pop());
println!("남은 접시: {:?}", plates);
}

실행 결과:

꺼낸 접시: Some("빨강")
꺼낸 접시: Some("초록")
남은 접시: ["파랑"]
▶ 직접 실행해 보기

pop()은 언제나 마지막에 넣은 항목을 꺼내요. 쌓아 둔 접시에서 맨 위의 접시를 집는 것과 같아요. 그냥 값이 아니라 Some(...)을 돌려주는 이유는 벡터가 비어 있을 수도 있기 때문이에요. 그럴 때는 프로그램이 멈추는 대신 None을 받아요.

  • Vec<T>는 크기가 늘어나는 목록이에요. vec![...]나 Vec::new()로 만들어요.
  • .push()는 끝에 추가하고, .pop()은 마지막 항목을 삭제하고, .remove(i)는 인덱스로 삭제해요.
  • vec[i]는 잘못된 인덱스에서 패닉을 일으키지만, .get(i)는 대신 Option을 돌려줘서 패닉이 일어나지 않아요.
  • for item in &vec으로 순회하면 벡터를 소비하는 대신 항목을 대여해요.

이해도 점검

1. numbers[10]처럼 잘못된 인덱스로 Vec에 접근하면 어떻게 될까요?

2. 패닉 걱정 없이 인덱스로 값을 찾을 수 있는 메서드는 무엇일까요?

3. for n in &numbers에서 &는 무슨 일을 할까요?

점수: 0 / 3