반복자 문서의 검증 코드다.
//! 21 반복자: 게으름(실행 순서 추적, 쓰지 않은 map 경고), 직접 만든 Iterator, 어댑터 사슬,
//! collect로 여러 타입 만들기(Vec·String·HashSet·HashMap·Result·Option), iter·iter_mut·into_iter가 내주는 타입,
//! 2021 에디션부터 배열의 into_iter가 값을 내준다는 것, 무한 반복자와 take, clippy needless_range_loop를 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/21_iterators_verify.rs"
use std::any::type_name;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::sync::atomic::{AtomicUsize, Ordering};
static SNIPPET: AtomicUsize = AtomicUsize::new(0);
fn compile_output(code: &str, edition: &str) -> String {
compile_with("rustc", code, edition)
}
/// rustc 또는 clippy-driver로 코드 조각을 컴파일해 stderr를 돌려준다.
fn compile_with(tool: &str, code: &str, edition: &str) -> String {
let n = SNIPPET.fetch_add(1, Ordering::Relaxed);
let dir = std::env::temp_dir().join(format!("vr21_{}_{}", std::process::id(), n));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("snippet.rs");
std::fs::write(&file, code).unwrap();
let out = std::process::Command::new(tool)
.args([
"--edition",
edition,
"--crate-type=lib",
"--emit=metadata",
"--out-dir",
])
.arg(&dir)
.arg(&file)
.output()
.expect("rustc 실행");
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn type_of<T>(_: &T) -> &'static str {
type_name::<T>()
}
/// 1부터 n까지 세는 반복자. next만 만들면 나머지 메서드는 공짜로 생긴다.
struct Counter {
n: u32,
max: u32,
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.n < self.max {
self.n += 1;
Some(self.n)
} else {
None
}
}
}
fn main() {
// 1. 게으름: 소비하는 메서드를 부르기 전에는 아무 일도 안 한다.
let mut log = Vec::new();
let it = [1, 2, 3, 4].iter().map(|x| {
log.push(format!("map {x}"));
x * 10
});
let it = it.filter(|x| *x > 15);
// 여기까지 log는 비어 있다(빌림 때문에 지금은 볼 수 없지만, 아래 결과 순서가 증거다).
let first: Vec<i32> = it.take(2).collect();
assert_eq!(first, vec![20, 30]);
println!("결과 {first:?}, 실행 기록 {log:?}");
// 4는 map조차 되지 않았다: take(2)가 두 개를 얻자 멈췄다.
assert_eq!(log, ["map 1", "map 2", "map 3"]);
let warn = compile_output("pub fn f(v: &[i32]) { v.iter().map(|x| x + 1); }", "2024");
let line = warn.lines().find(|l| l.starts_with("warning")).unwrap();
println!("[쓰지 않은 map] {line}");
assert!(warn.contains("unused `Map` that must be used"));
assert!(warn.contains("iterators are lazy and do nothing unless consumed"));
// 2. 직접 만든 반복자 + 공짜로 얻는 어댑터
let total: u32 = Counter { n: 0, max: 5 }
.zip(Counter { n: 0, max: 5 }.skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
// (1,2)(2,3)(3,4)(4,5) -> 2,6,12,20 -> 3의 배수 6,12 -> 18
assert_eq!(total, 18);
println!("Counter zip 합 = {total}");
// 3. collect: 받을 타입이 결과를 정한다.
let words = ["apple", "bob", "apple", "cat"];
let v: Vec<&str> = words.iter().filter(|w| w.len() > 3).copied().collect();
let s: String = words.iter().map(|w| w.chars().next().unwrap()).collect();
let set: HashSet<&str> = words.iter().copied().collect();
let freq: HashMap<&str, usize> = words.iter().fold(HashMap::new(), |mut m, w| {
*m.entry(*w).or_insert(0) += 1;
m
});
let sorted: BTreeMap<&str, usize> = freq.iter().map(|(k, v)| (*k, *v)).collect();
assert_eq!(v, ["apple", "apple"]);
assert_eq!(s, "abac");
assert_eq!(set.len(), 3);
assert_eq!(freq["apple"], 2);
println!("String {s:?}, 종류 {}개, 빈도 {sorted:?}", set.len());
let good: Result<Vec<i32>, _> = ["1", "2", "3"].iter().map(|s| s.parse::<i32>()).collect();
let bad: Result<Vec<i32>, _> = ["1", "x", "3", "y"]
.iter()
.map(|s| s.parse::<i32>())
.collect();
assert_eq!(good, Ok(vec![1, 2, 3]));
assert!(bad.is_err());
let opt: Option<Vec<u32>> = ["7", "8"].iter().map(|s| s.parse().ok()).collect();
assert_eq!(opt, Some(vec![7, 8]));
// 첫 에러에서 멈춘다: 몇 개를 봤는지 센다.
let mut seen = 0;
let r: Result<Vec<i32>, std::num::ParseIntError> = ["1", "x", "3", "y"]
.iter()
.inspect(|_| seen += 1)
.map(|s| s.parse::<i32>())
.collect();
assert!(r.is_err());
println!("Result로 collect: 본 원소 {seen}개에서 멈춤");
assert_eq!(seen, 2);
// 4. iter / iter_mut / into_iter
let mut names = vec![String::from("a"), String::from("b")];
let mut t1 = String::new();
for r in names.iter() {
t1 = type_of(&r).to_string();
}
for n in names.iter_mut() {
n.push('!');
}
let t3 = type_of(&names.clone().into_iter().next().unwrap()).to_string();
println!("iter -> {t1}, into_iter -> {t3}");
assert_eq!(t1, "&alloc::string::String");
assert_eq!(t3, "alloc::string::String");
assert_eq!(names, ["a!", "b!"]);
let moved: Vec<String> = names.into_iter().collect();
assert_eq!(moved.len(), 2);
// names는 이제 쓸 수 없다 (into_iter가 가져갔다).
// for 문은 into_iter를 부른다: &v는 참조를, v는 값을 내준다.
let nums = vec![1, 2, 3];
let mut acc = 0;
for x in &nums {
acc += *x;
}
assert_eq!(acc, 6);
// 5. 배열의 into_iter: 2021부터 값을 내준다.
let arr = [10u8, 20, 30];
let first = arr.into_iter().next().unwrap();
assert_eq!(type_of(&first), "u8");
let e2018 = compile_output(
"pub fn f() -> u8 { let a = [1u8, 2]; let x: u8 = a.into_iter().next().unwrap(); x }",
"2018",
);
let e2024 = compile_output(
"pub fn f() -> u8 { let a = [1u8, 2]; let x: u8 = a.into_iter().next().unwrap(); x }",
"2024",
);
println!(
"[배열 into_iter 2018] {}",
e2018
.lines()
.find(|l| l.starts_with("error"))
.unwrap_or("(오류 없음)")
);
assert!(e2018.contains("error[E0308]"));
assert!(e2018.contains("expected `u8`, found `&u8`"));
assert!(!e2024.contains("error"));
// 6. 끝이 없는 반복자도 take로 끊으면 된다(게으르기 때문).
let squares: Vec<u64> = (1u64..).map(|n| n * n).take(5).collect();
assert_eq!(squares, [1, 4, 9, 16, 25]);
// 7. clippy: 번호로만 원소를 읽는 for 문은 반복자로 바꾸라고 한다.
let w = compile_with(
"clippy-driver",
"pub fn s(v: &[i32]) -> i32 { let mut t = 0; for i in 0..v.len() { t += v[i]; } t }",
"2024",
);
let first = w.lines().find(|l| l.starts_with("warning")).unwrap();
println!("[clippy] {first}");
assert!(w.contains("#[warn(clippy::needless_range_loop)]` on by default"));
// 8. 출력 예측 카드
let out: Vec<(usize, char)> = "rust"
.chars()
.rev()
.enumerate()
.filter(|(i, _)| i % 2 == 0)
.collect();
println!("{out:?}");
assert_eq!(out, vec![(0, 't'), (2, 'u')]);
let chained: i32 = (1..=10)
.filter(|n| n % 2 == 1)
.map(|n| n * n)
.take_while(|&n| n < 50)
.sum();
assert_eq!(chained, 1 + 9 + 25 + 49);
println!("홀수 제곱 중 50 미만의 합 = {chained}");
println!("ALL CHECKS PASSED");
}