🧺 Rust 기본 컬렉션 완전 정복 – Vec, String, HashMap
Rust에서 가장 널리 쓰이는 세 가지 컬렉션,
Vec<T>, String, HashMap<K, V>는
기초이자 실전에서도 계속 마주하게 되는 핵심 도구입니다.
이번 글에서는 이 컬렉션들이 어떻게 동작하는지,
언제 어떤 상황에서 쓰는지, 그리고 실제 코드에서는 어떻게 활용하는지
조금 더 깊이 있게 다뤄보겠습니다.
1️⃣ Vec – 순서 있는, 빠르고 안전한 배열
✅ 어떤 컬렉션인가요?
Vec<T>는 동일한 타입의 값을 순서대로 저장하는 가변 배열입니다.
크기를 미리 알 수 없거나, 중간에 값을 추가/삭제해야 하는 경우 유용합니다.
- 내부적으로는 힙(heap)에 연속된 메모리 공간을 할당합니다.
- C++의 std::vector, Java의 ArrayList, Python의 리스트와 유사합니다.
🛠️ 다양한 생성 방식
let mut v1: Vec<i32> = Vec::new(); // 빈 벡터
let v2 = vec![1, 2, 3]; // 매크로 사용
let v3 = Vec::with_capacity(100); // 용량 미리 확보
Vec::with_capacity(n)은 n개 저장할 공간을 미리 할당해서 성능적으로 많은 push()가 예상될 때 유리합니다.
🧩 요소 추가와 제거
let mut v = vec![10];
v.push(20);
v.insert(0, 5); // 앞에 삽입
v.remove(1); // 인덱스로 삭제
let last = v.pop(); // 마지막 제거
🔍 요소 접근 방법
let x = v[0]; // 직접 접근 (주의: 범위 벗어나면 panic!)
let y = v.get(1); // 안전한 접근 (Option 반환)
일반적으로는 get(i) 방식이 더 안전하고 실무에서도 권장돼요.
🔁 반복자 사용
for val in &v { println!("{val}"); } // 불변 참조
for val in &mut v { *val *= 2; } // 가변 참조
for val in v.into_iter() { println!("{val}"); } // 소유권 이동
🧠 고급 기능 예시
let evens: Vec<_> = v.into_iter().filter(|x| x % 2 == 0).collect();
let mut v = vec![5, 2, 8, 3];
v.sort(); // 정렬
✅ 실전 팁 요약
상황 | 추천 방식 |
빠른 초기화 | vec![1, 2, 3] |
접근은 항상 안전하게 | v.get(i) |
용량이 클 경우 | with_capacity()로 시작 |
반복 시 참조 우선 | for val in &v |
함수 인자는 &[T] | 벡터 → 슬라이스 변환으로 유연한 인터페이스 가능 |
2️⃣ String – 안전하고 강력한 UTF-8 문자열
✅ 어떤 컬렉션인가요?
Rust의 String은 가변 길이의 UTF-8 문자열입니다. C 스타일 문자열(\0 종료)이 아니라,
명확한 소유권과 안전한 경계를 보장하는 구조입니다. Rust의 문자열 관련 타입은 두 가지가 있어요:
- &str: 읽기 전용 문자열 슬라이스
- String: heap에 저장되는 가변 문자열
✨ 문자열 생성
let s1 = String::new();
let s2 = String::from("hello");
let s3 = "world".to_string();
let s4: String = "hi".into(); // 더 간결한 변환
➕ 추가 및 수정
let mut s = String::from("Hi");
s.push('!'); // 문자 추가
s.push_str(" Rust"); // 문자열 추가
let name = "world";
let msg = format!("Hello, {name}!"); // 문자열 연결 (권장)
🔍 부분 문자열, 문자 반복
for ch in s.chars() {
println!("{ch}"); // 유니코드 문자 단위로 반복
}
let slice = &s[0..2]; // 문자열 슬라이스 (UTF-8 경계 주의)
한글, 이모지처럼 UTF-8 문자당 바이트 수가 다르기 때문에
슬라이스 시 바이트 경계를 잘못 잡으면 panic이 발생할 수 있어요.
✅ 실전 팁 요약
작업 | 권장 방식 |
문자열 결합 | format! |
한 글자씩 처리 | .chars() |
함수 인자 타입 | &str |
문자열 복사 | "text".to_string() 또는 "text".into() |
문자열 자르기 | 슬라이스보단 .split_at()이나 .get(..) 권장 |
3️⃣ HashMap<K, V> – 빠른 키-값 저장소
✅ 어떤 컬렉션인가요?
HashMap<K, V>는 키와 값의 쌍을 저장하는 자료구조입니다. 빠른 조회와 삽입/수정을 위해 해시 함수를 기반으로 작동하며,
순서는 보장되지 않습니다.
✨ 생성 및 삽입
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("apple", 3);
map.insert("banana", 5);
🔍 값 조회
if let Some(&count) = map.get("apple") {
println!("개수: {count}");
}
➕ 기본값 삽입 (entry API)
map.entry("orange").or_insert(0); // 없으면 0으로 넣기
*map.entry("banana").or_insert(0) += 1; // 값 증가
.entry()는 실무에서 자주 쓰는 패턴입니다.
키가 없을 때 기본값을 설정하고, 있으면 수정까지 한 번에 가능해요.
🔁 반복
for (key, val) in &map {
println!("{key} => {val}");
}
✅ 실전 팁 요약
작업 | 권장 방식 |
값이 없을 때 | .entry().or_insert() |
전체 순회 | for (k, v) in &map |
문자열 키 사용 | &str or String 모두 가능 |
키가 커스텀 타입일 때 | Eq + Hash 트레이트 구현 필요 |
키 순서가 중요하다면 | BTreeMap 사용 고려 |
'개발 이야기 > [스터디] Rust' 카테고리의 다른 글
[러스트 프로그래밍 공식 가이드] Chapter 10. 제네릭, 트레이트, 수명 이해하기 (5) | 2025.07.09 |
---|---|
[러스트 프로그래밍 공식 가이드] Chapter 9. 에러 처리 (0) | 2025.07.08 |
[러스트 프로그래밍 공식 가이드] Chapter 7. 러스트 프로젝트 구조 쉽게 이해하기 – 패키지, 크레이트, 모듈, 경로 (1) | 2025.07.03 |
[러스트 프로그래밍 공식 가이드] Chapter 6. 열거자와 패턴 매칭 (0) | 2025.06.27 |
[러스트 프로그래밍 공식 가이드] Chapter 5. 구조체를 활용한 관련 데이터의 구조화 (1) | 2025.06.26 |