2-1. collections
· 5분 읽기
Day 14-15: Collections 정리
핵심 한줄: Rust의 컬렉션은 Python과 비슷하게 동작하지만, 소유권과 빌림 규칙이 결합되어 안전한 데이터 관리를 강제한다.
🔑 핵심 개념
1. Vec — Python의 list
let mut scores: Vec<i32> = vec![85, 92, 78, 95, 88];
scores.push(76); // 추가
scores.pop(); // 마지막 제거
println!("{}", scores[0]); // 인덱싱
println!("{:?}", scores.get(100)); // 안전한 접근 → None소유권과의 관계:
vec.push(val)→ val의 소유권이 Vec으로 이동 (Copy 타입 제외)- push 후 원본 변수 사용 불가 (String 등 Heap 타입)
let name = String::from("Alice");
names.push(name);
// println!("{}", name); // ❌ 소유권 이동됨!
println!("{}", names[0]); // ✅ Vec에서 빌려서 접근해결 방법 3가지:
- push 전에 먼저 사용
name.clone()으로 복사본을 push- Vec 안의 데이터를 빌려서(
&) 접근
2. Vec 순회 — 3가지 방식
| 방식 | 문법 | 타입 | 원본 이후 사용 |
|---|---|---|---|
| 불변 빌림 | for x in &vec | &T | ✅ 가능 |
| 가변 빌림 | for x in &mut vec | &mut T | ✅ 가능 |
| 소유권 소비 | for x in vec | T | ❌ 소비됨 |
기본 원칙:
&vec으로 빌려서 순회하는 것을 기본으로 하고, 필요할 때만&mut이나 소비 사용
// iter() = for x in &vec — 불변 빌림
// iter_mut() = for x in &mut vec — 가변 빌림
// into_iter() = for x in vec — 소유권 소비3. HashMap<K, V> — Python의 dict
use std::collections::HashMap;
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Alice"), 95);조회 — 2가지 방식:
| 방법 | 반환 타입 | key 없을 때 |
|---|---|---|
scores["key"] | 값 직접 | panic! 💥 |
scores.get("key") | Option<&V> | None ✅ |
습관:
get()을 기본으로 사용 — Pythondict.get(key)와 동일한 안전 패턴
insert with String, query with &str:
scores.insert(String::from("Alice"), 95); // 삽입: String (소유권 필요)
scores.get("Alice"); // 조회: &str (비교만 하면 됨)이게 가능한 이유: Deref coercion — String이 내부적으로 &str로 자동 변환
4. entry() API — HashMap의 핵심 패턴
"key가 있으면 수정, 없으면 삽입"을 한 줄로 처리:
*word_count.entry(word).or_insert(0) += 1;분해:
entry(word)→ key의 "자리"를 잡음.or_insert(0)→ 비어있으면 0 삽입, 기존이면 그대로 →&mut V반환*→ 역참조로 실제 값 접근+= 1→ 값 증가
entry API 변형:
| 패턴 | 용도 |
|---|---|
.or_insert(값) | 없으면 기본값 삽입 |
.or_insert_with(|| 계산) | 없으면 클로저로 값 생성 |
.or_default() | 없으면 타입의 Default 값 |
복합 구조에서의 활용:
// HashMap<String, Vec<f64>> — 학생별 점수 목록
grades.entry("Alice".to_string()).or_insert(vec![]).push(95.0);
// 없으면 빈 Vec 생성 → 있든 없든 push5. HashSet — Python의 set
중복 없는 값의 집합. HashMap에서 value를 뺀 것:
use std::collections::HashSet;
let mut fruits: HashSet<String> = HashSet::new();
fruits.insert(String::from("apple"));
fruits.insert(String::from("apple")); // 중복 → 무시
println!("{}", fruits.len()); // 1집합 연산:
let union = a.union(&b); // 합집합
let intersection = a.intersection(&b); // 교집합
let difference = a.difference(&b); // 차집합collect()의 타입에 따른 결과 변화:
let words: Vec<&str> = text.split_whitespace().collect(); // 중복 포함
let words: HashSet<&str> = text.split_whitespace().collect(); // 중복 제거6. String vs &str 심화
String | &str | |
|---|---|---|
| 소유권 | 소유 (힙에 저장) | 빌림 (어딘가를 가리킴) |
| 변경 | 가변 가능 | 변경 불가 |
| 내부 | UTF-8 바이트 시퀀스 | UTF-8 바이트 슬라이스 |
UTF-8과 인덱싱:
let hello = String::from("안녕하세요");
hello.len() // 15 (바이트 수 — 한글 3바이트 × 5)
hello.chars().count() // 5 (글자 수)
// hello[0] // ❌ 컴파일 에러! 바이트 인덱싱은 위험
len()= 바이트 수,chars().count()= 글자 수. 한글은 UTF-8에서 글자당 3바이트.
글자 접근 방법:
hello.chars().nth(0) // Some('안')
&hello[0..6] // "안녕" (3+3=6바이트, 글자 경계 맞춰야!)
for ch in hello.chars() { } // 글자 단위 순회메서드 구분 — 원본 수정 vs 새 String 반환:
| 메서드 | 원본 수정? | 시그니처 |
|---|---|---|
push_str(), push() | ✅ 수정 | &mut self |
replace(), to_uppercase() | ❌ 새 String 반환 | &self |
7. 파일 I/O — std::fs
use std::fs;
// 쓰기
fs::write("file.txt", "내용").unwrap();
// 읽기
let content = fs::read_to_string("file.txt").unwrap();반환 타입은
Result<T, E>— 파일이 없거나 권한 문제 등으로 실패할 수 있으므로
Option vs Result:
| 용도 | 성공 | 실패 | |
|---|---|---|---|
Option | 값이 있을 수도 없을 수도 | Some(값) | None |
Result | 작업이 성공/실패 | Ok(값) | Err(에러) |
| 예시 | 반환 타입 |
|---|---|
HashMap.get("key") | Option |
fs::read_to_string() | Result |
"42".parse::<i32>() | Result |
⚠️ Python 개발자 주의사항
vec.push(val)후 val 사용 불가 — Pythonlist.append(x)후에도 x 사용 가능하지만, Rust는 소유권이 이동됨 (Copy 타입 제외)map["key"]는 panic 가능 — Pythondict[key]의KeyError와 동일한 위험.get()을 습관화for item in vec은 소유권 소비 — Python에서는for item in list후에도 list 사용 가능. Rust에서는for item in &vec으로 빌려서 순회String[0]불가 — Pythonstr[0]과 달리 UTF-8 바이트 경계 문제로 인덱싱 금지unwrap()남발 금지 — 학습 초기에는 괜찮지만,match나if let으로 처리하는 습관 중요
📝 Day 14-15 마스터 체크리스트
- Vec의 소유권 이동과 빌려서 순회하는 패턴 이해
- HashMap의 안전한 조회 (
get()) 습관화 - entry() API로 "없으면 삽입, 있으면 수정" 패턴 사용 가능
- HashSet으로 중복 제거, collect()의 타입 추론 활용
- String의 UTF-8 특성과
len()vschars().count()구분 - 원본 수정 메서드(
&mut self) vs 새 값 반환 메서드(&self) 구분 - iter() / iter_mut() / into_iter() 3가지 순회 방식 구분
- 파일 I/O 기본 패턴과 Result 반환 이해
- Option vs Result 구분