본문으로 건너뛰기

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가지:

  1. push 전에 먼저 사용
  2. name.clone()으로 복사본을 push
  3. Vec 안의 데이터를 빌려서(&) 접근

2. Vec 순회 — 3가지 방식

방식문법타입원본 이후 사용
불변 빌림for x in &vec&T✅ 가능
가변 빌림for x in &mut vec&mut T✅ 가능
소유권 소비for x in vecT❌ 소비됨

기본 원칙: &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()을 기본으로 사용 — Python dict.get(key)와 동일한 안전 패턴

insert with String, query with &str:

scores.insert(String::from("Alice"), 95);  // 삽입: String (소유권 필요)
scores.get("Alice");                        // 조회: &str (비교만 하면 됨)

이게 가능한 이유: Deref coercionString이 내부적으로 &str로 자동 변환


4. entry() API — HashMap의 핵심 패턴

"key가 있으면 수정, 없으면 삽입"을 한 줄로 처리:

*word_count.entry(word).or_insert(0) += 1;

분해:

  1. entry(word) → key의 "자리"를 잡음
  2. .or_insert(0) → 비어있으면 0 삽입, 기존이면 그대로 → &mut V 반환
  3. * → 역참조로 실제 값 접근
  4. += 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 생성 → 있든 없든 push

5. 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 개발자 주의사항

  1. vec.push(val) 후 val 사용 불가 — Python list.append(x) 후에도 x 사용 가능하지만, Rust는 소유권이 이동됨 (Copy 타입 제외)
  2. map["key"]는 panic 가능 — Python dict[key]KeyError와 동일한 위험. get()을 습관화
  3. for item in vec은 소유권 소비 — Python에서는 for item in list 후에도 list 사용 가능. Rust에서는 for item in &vec으로 빌려서 순회
  4. String[0] 불가 — Python str[0]과 달리 UTF-8 바이트 경계 문제로 인덱싱 금지
  5. unwrap() 남발 금지 — 학습 초기에는 괜찮지만, matchif let으로 처리하는 습관 중요

📝 Day 14-15 마스터 체크리스트

  • Vec의 소유권 이동과 빌려서 순회하는 패턴 이해
  • HashMap의 안전한 조회 (get()) 습관화
  • entry() API로 "없으면 삽입, 있으면 수정" 패턴 사용 가능
  • HashSet으로 중복 제거, collect()의 타입 추론 활용
  • String의 UTF-8 특성과 len() vs chars().count() 구분
  • 원본 수정 메서드(&mut self) vs 새 값 반환 메서드(&self) 구분
  • iter() / iter_mut() / into_iter() 3가지 순회 방식 구분
  • 파일 I/O 기본 패턴과 Result 반환 이해
  • Option vs Result 구분

🔗 참고 자료