이 문서는 로드맵 5단계를 코드 예제와 함께 상세히 풀어놓은 교재입니다. 실무 Rust 코드에서 가장 자주 쓰는 문법 묶음입니다. 소유권 관점(
iter/into_iter/iter_mut)을 먼저 고정한 뒤, 어댑터 체인과 커스텀 이터레이터로 갑니다.
선수: 2단계 소유권, 4단계 Result 체이닝
fn main() {
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
let mut v = vec![1, 2, 3]; // 매크로 — 타입·용량 추론
v.extend([4, 5]);
println!("len={}, cap={}", v.len(), v.capacity());
println!("첫 원소 = {}", v[0]); // 없으면 panic
println!("안전한 접근 = {:?}", v.get(10)); // Option<&T>
// 슬라이스로 함수에 넘기기 (2단계 복습)
print_slice(&v);
print_slice(&v[1..3]);
}
fn print_slice(s: &[i32]) {
println!("{s:?}");
}자주 쓰는 API:
| 메서드 | 역할 |
|---|---|
push / pop |
끝에서 추가·제거 |
insert / remove |
중간 — O(n), 자주 쓰면 다른 구조 검토 |
extend / append |
여러 원소 합치기 |
retain |
조건에 맞는 것만 남기기 |
drain(..) |
구간을 빼내며 이터레이터로 |
split_at_mut |
겹치지 않는 가변 슬라이스 둘 |
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
v.retain(|&x| x % 2 == 1);
println!("{v:?}"); // [1, 3, 5]
let mut v = vec![10, 20, 30, 40];
let drained: Vec<_> = v.drain(1..3).collect();
println!("drained={drained:?}, left={v:?}"); // [20,30], [10,40]
}use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);
// 조회
let s = scores.get("Blue"); // Option<&i32>
let s = scores.get("Blue").copied().unwrap_or(0);
// entry API — 없으면 넣고, 있으면 수정 (실무 단골)
scores.entry(String::from("Yellow")).or_insert(30);
let blue = scores.entry(String::from("Blue")).or_insert(0);
*blue += 5;
for (k, v) in &scores {
println!("{k}: {v}");
}
}entry 패턴으로 단어 빈도:
use std::collections::HashMap;
fn word_count(text: &str) -> HashMap<&str, usize> {
let mut map = HashMap::new();
for w in text.split_whitespace() {
*map.entry(w).or_insert(0) += 1;
}
map
}키는 Eq + Hash가 필요합니다. String/i32/&str(수명 주의) 등이 흔합니다.
use std::collections::HashSet;
fn main() {
let mut set = HashSet::from([1, 2, 3]);
set.insert(2); // false — 이미 있음
set.insert(4);
println!("{}", set.contains(&3));
let a = HashSet::from([1, 2, 3]);
let b = HashSet::from([2, 3, 4]);
println!("{:?}", a.intersection(&b).collect::<Vec<_>>()); // 2, 3
println!("{:?}", a.union(&b).collect::<Vec<_>>());
println!("{:?}", a.difference(&b).collect::<Vec<_>>());
}use std::collections::BTreeMap;
fn main() {
let mut map = BTreeMap::new();
map.insert(3, "c");
map.insert(1, "a");
map.insert(2, "b");
// 키 순서 보장 — 순회·range에 유리
for (k, v) in &map {
println!("{k}: {v}");
}
for (k, v) in map.range(2..) {
println!("range {k}: {v}");
}
}HashMap |
BTreeMap |
|
|---|---|---|
| 순서 | 없음(랜덤화) | 키 정렬 |
| 평균 연산 | O(1) | O(log n) |
| 키 요구 | Eq + Hash |
Ord |
| 언제 | 기본값, 빈도수 | 범위 쿼리, 안정적 순서 |
-
Vec에서getvs 인덱싱 차이를 panic으로 체감 -
entry().or_insert로 문자 빈도수 맵 만들기 - 같은 데이터를
HashMap과BTreeMap에 넣고 순회 순서 비교
컬렉션을 순회하는 세 가지 길이 무엇을 빌리거나 소비하느냐입니다.
fn main() {
let mut v = vec![String::from("a"), String::from("b")];
// 1) iter() → Iterator<Item = &T> (불변 빌림)
for s in v.iter() {
println!("len={}", s.len());
}
// v 여전히 사용 가능
// 2) iter_mut() → Iterator<Item = &mut T>
for s in v.iter_mut() {
s.push('!');
}
// 3) into_iter() → Iterator<Item = T> (소유권 이동, v 소비)
for s in v.into_iter() {
println!("{s}");
}
// println!("{v:?}"); // 에러 — moved
}for x in collection은 **IntoIterator**를 호출합니다.
| 표현 | 대략 동일 | Item |
|---|---|---|
for x in &v |
v.iter() |
&T |
for x in &mut v |
v.iter_mut() |
&mut T |
for x in v |
v.into_iter() |
T |
HashMap도 같습니다:
use std::collections::HashMap;
fn main() {
let map = HashMap::from([("a", 1), ("b", 2)]);
for (k, v) in &map { // (&K, &V)
println!("{k}: {v}");
}
for (k, v) in map { // (K, V) — map 소비
println!("{k}: {v}");
}
}- 같은
Vec<String>에 세 가지 순회를 쓰고, 어떤 게 소비인지 확인 -
for x in v후v를 써서 move 에러를 읽어보기
이터레이터는 lazy입니다. collect / sum / for / count 같은 consuming adapter를 만나야 실제로 돌아갑니다.
fn main() {
let v = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = v
.iter()
.filter(|x| *x % 2 == 0)
.map(|x| x * 10)
.collect();
println!("{result:?}"); // [20, 40, 60]
}fn main() {
let v = vec!["10", "x", "20", "30"];
// map — 각 원소 변환
let lens: Vec<_> = v.iter().map(|s| s.len()).collect();
// filter — 조건
let even: Vec<_> = (0..10).filter(|x| x % 2 == 0).collect();
// filter_map — filter + map + Option 펼치기
let nums: Vec<i32> = v
.iter()
.filter_map(|s| s.parse().ok())
.collect();
// enumerate — (index, item)
for (i, x) in nums.iter().enumerate() {
println!("{i}: {x}");
}
// zip — 두 이터레이터를 쌍으로 (짧은 쪽에 맞춤)
let a = [1, 2, 3];
let b = ["a", "b"];
let pairs: Vec<_> = a.iter().zip(b.iter()).collect();
println!("{pairs:?}");
// flat_map — 하나 → 여러 개로 펼침
let words = ["hello world", "rust lang"];
let tokens: Vec<&str> = words.iter().flat_map(|s| s.split_whitespace()).collect();
// fold / reduce — 누적
let sum = (1..=10).fold(0, |acc, x| acc + x);
let sum2: i32 = (1..=10).sum(); // 전용 메서드
// take / skip / chain
let first3: Vec<_> = (0..100).take(3).collect();
let skipped: Vec<_> = (0..5).skip(2).collect();
let chained: Vec<_> = (0..2).chain(10..12).collect();
let _ = (lens, even, tokens, sum, sum2, first3, skipped, chained);
}collect의 반환 타입은 여러 가능성이 있어 종종 명시가 필요합니다.
fn main() {
let v = vec![1, 2, 3];
let a: Vec<_> = v.iter().map(|x| x * 2).collect();
let b = v.iter().map(|x| x * 2).collect::<Vec<_>>();
let c: std::collections::HashSet<_> = v.iter().copied().collect();
// Result를 collect하면 하나라도 Err면 전체가 Err — 실무 보석
let parsed: Result<Vec<i32>, _> = ["1", "2", "x"]
.iter()
.map(|s| s.parse::<i32>())
.collect();
println!("{parsed:?}"); // Err(...)
let _ = (a, b, c);
}collect::<Result<Vec<_>, _>>() 패턴은 4단계 ?와 자주 짝을 이룹니다.
-
filter+map+collect로 짝수만 제곱한Vec만들기 -
filter_map으로 파싱 가능한 줄만 모으기 -
map(parse).collect::<Result<Vec<_>, _>>()성공/실패 케이스 확인
필수 구현은 type Item과 fn next(&mut self) -> Option<Self::Item> 하나면 됩니다. 나머지 어댑터(map 등)는 자동으로 따라옵니다.
struct Counter {
current: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Self {
Self { current: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
let v = self.current;
self.current += 1;
Some(v)
} else {
None
}
}
}
fn main() {
let sum: usize = Counter::new(5).sum(); // 0+1+2+3+4 = 10
let evens: Vec<_> = Counter::new(10).filter(|x| x % 2 == 0).collect();
println!("sum={sum}, evens={evens:?}");
}슬라이스/범위를 감싼 이터레이터:
struct Windows<'a, T> {
slice: &'a [T],
size: usize,
index: usize,
}
impl<'a, T> Windows<'a, T> {
fn new(slice: &'a [T], size: usize) -> Self {
Self {
slice,
size,
index: 0,
}
}
}
impl<'a, T> Iterator for Windows<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
if self.index + self.size > self.slice.len() {
return None;
}
let window = &self.slice[self.index..self.index + self.size];
self.index += 1;
Some(window)
}
}
fn main() {
let data = [1, 2, 3, 4, 5];
for w in Windows::new(&data, 3) {
println!("{w:?}");
}
// 표준: data.windows(3) 과 같은 아이디어
}-
Countdown(n)이터레이터: n, n-1, …, 1 후None - 그 위에
.map(|x| x * x).collect::<Vec<_>>()체이닝
로드맵 실습: 파일(또는 문자열)에서 읽은 텍스트를 이터레이터 체인만으로 단어 빈도 집계.
use std::collections::HashMap;
use std::fs;
fn word_frequencies(text: &str) -> HashMap<String, usize> {
text
.split_whitespace()
.map(|w| {
w.chars()
.filter(|c| c.is_alphanumeric())
.flat_map(|c| c.to_lowercase())
.collect::<String>()
})
.filter(|w| !w.is_empty())
.fold(HashMap::new(), |mut acc, word| {
*acc.entry(word).or_insert(0) += 1;
acc
})
}
/// 빈도 내림차순 Top N
fn top_n(freq: &HashMap<String, usize>, n: usize) -> Vec<(&String, &usize)> {
let mut items: Vec<_> = freq.iter().collect();
items.sort_by(|a, b| b.1.cmp(a.1).then_with(|| a.0.cmp(b.0)));
items.into_iter().take(n).collect()
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 파일이 있으면 읽고, 없으면 샘플 사용
let text = fs::read_to_string("input.txt").unwrap_or_else(|_| {
String::from(
"Rust rust RUST is fun. Iterators in Rust are lazy and powerful. Fun fun fun!",
)
});
let freq = word_frequencies(&text);
// for 루프 없이 출력도 체인으로
top_n(&freq, 5)
.iter()
.enumerate()
.for_each(|(i, (word, count))| {
println!("{}. {word} — {count}", i + 1);
});
Ok(())
}포인트:
split→map정규화 →filter→fold/entry— 루프 없이 집계.- 정규화에서
flat_map(to_lowercase)— 유니코드 소문자가 여러 char일 수 있음. sort_by+take로 Top N.- 파일 IO는 4단계
Result/unwrap_or_else와 연결.
for_each는 사이드이펙트용 consuming adapter입니다. 값을 모을 땐 collect가 맞습니다.
-
BTreeMap으로 바꿔 알파벳 순 출력 - 최소 길이 N 미만 단어 제외 (
filter) -
lines().flat_map(split_whitespace)로 줄 단위 파일 스트리밍 느낌 내기 - 파싱 실패 줄을
filter_map으로 건너뛰는 로그 카운터 버전
Vec / HashMap / HashSet / BTreeMap
iter(&T) iter_mut(&mut T) into_iter(T)
Iterator = lazy
adapters: map filter filter_map flat_map zip enumerate take fold …
consume: collect sum count for_each any all …
collect::<Vec<_>>() / collect::<Result<Vec<_>, _>>()
custom: impl Iterator { type Item; fn next }
6단계 클로저에서는 |x| ... 문법과 Fn / FnMut / FnOnce, move 클로저를 다룹니다. 이 단계의 map/filter에 넘긴 그 인자들의 정체입니다.