Rust 초급 9편: 벡터·해시맵·문자열로 학습 기록 모으기
지금까지는 기록 하나를 주로 다뤘습니다. 이번에는 여러 번의 학습 시간을 모으고 주제별 합계를 계산합니다. 순서가 있는 목록에는 Vec, 이름으로 찾는 집계에는 HashMap을 사용해 보겠습니다.

지금까지는 기록 하나를 주로 다뤘습니다. 이번에는 여러 번의 학습 시간을 모으고 주제별 합계를 계산합니다. 순서가 있는 목록에는 Vec, 이름으로 찾는 집계에는 HashMap을 사용해 보겠습니다.
준비와 목표
이전 편의 for, String, Option을 사용합니다. 새 프로젝트를 만든 뒤 핵심 코드를 src/main.rs에 저장합니다.
cargo new study_collections
cd study_collections목표는 값 추가·조회·순회를 직접 해 보고, 문자열의 바이트 길이와 문자 순회를 구분하는 것입니다.
핵심 실습: 주제별 학습 시간 합산하기
use std::collections::HashMap;
fn main() {
let mut sessions: Vec<(&str, u32)> = vec![
("Rust", 25),
("Rust", 35),
("CLI", 20),
];
sessions.push(("CLI", 10));
let mut totals: HashMap<&str, u32> = HashMap::new();
for (topic, minutes) in &sessions {
let total = totals.entry(*topic).or_insert(0);
*total += *minutes;
}
println!("기록 수: {}", sessions.len());
for topic in ["Rust", "CLI"] {
let minutes = totals.get(topic).copied().unwrap_or(0);
println!("{topic}: {minutes}분");
}
match sessions.get(9) {
Some((topic, minutes)) => println!("열 번째 기록: {topic} {minutes}분"),
None => println!("열 번째 기록은 없습니다"),
}
}cargo run기록 수: 4
Rust: 60분
CLI: 30분
열 번째 기록은 없습니다Vec<(&str, u32)>는 문자열 참조와 정수의 튜플을 담는 벡터입니다. 튜플은 여러 값을 순서로 묶는 형식이며, 여기서는 주제와 시간을 한 기록으로 묶었습니다. 같은 벡터의 항목들은 같은 타입입니다. vec!로 초기값을 만들고 push로 끝에 추가했습니다.
빌려서 순회하고, 범위를 확인하며 조회하기
for ... in &sessions는 벡터를 읽기 전용으로 빌려 순회합니다. 벡터 자체를 소비하지 않으므로 반복 뒤에도 len과 get을 호출할 수 있습니다. *topic과 *minutes는 참조가 가리키는 값을 읽습니다. 이번 값들은 복사할 수 있는 타입입니다.
get(9)는 열 번째 항목을 요청합니다. 인덱스는 0부터 시작합니다. 없는 인덱스에는 None이 오므로 match로 처리합니다. sessions9]처럼 직접 인덱싱하면 범위 밖에서 panic이 납니다. 외부 입력으로 정한 위치는 get으로 확인하는 습관이 도움이 됩니다. [벡터 공식 설명
HashMap의 entry 패턴
HashMap은 키에서 값을 찾는 자료구조입니다. entry(*topic).or_insert(0)은 주제가 있으면 그 누적값에 대한 가변 참조를, 없으면 0을 넣은 뒤 그 값에 대한 가변 참조를 반환합니다. *total += *minutes로 실제 누적값을 바꿉니다.
totals.get(topic)은 Option<&u32>를 반환합니다. copied()로 Option<u32>를 만들고, 없을 때의 기본값을 unwrap_or(0)으로 정했습니다. 이미 배운 Option이 조회 결과에도 사용됩니다.
HashMap 자체의 순회 순서는 고정되어 있지 않습니다. 예상 출력 순서를 유지하려고 이 예제는 "Rust", "CLI"] 순서대로 키를 조회합니다. HashMap을 그대로 출력하고 순서가 달라졌다고 계산 오류로 생각하지 마세요. [해시맵 공식 설명
한글 문자열의 길이와 순회
다음은 src/main.rs 전체를 바꿔 실행할 수 있는 별도 예제입니다.
fn main() {
let mut title = String::from("러스트");
title.push_str(" 입문");
println!("{title}");
println!("바이트: {}", title.len());
println!("스칼라 값 수: {}", title.chars().count());
for ch in title.chars() {
println!("{ch}");
}
}첫 세 줄은 다음과 같습니다. 이어서 문자별로 줄을 바꾸며, 공백도 한 줄로 출력합니다.
러스트 입문
바이트: 16
스칼라 값 수: 6한글 다섯 글자가 각각 3바이트이고 공백은 1바이트여서 16입니다. chars는 Unicode 스칼라 값 단위입니다. 사용자 눈에 보이는 글자 단위와 항상 일치하지는 않습니다.
오류를 확인해 보기
첫 예제에서 for ... in &sessions의 &를 지우면 벡터를 소비합니다. 뒤의 sessions.len()에서 이동한 값 사용 오류가 발생합니다. 반복 뒤에도 목록이 필요하다면 &로 빌립니다.
빈 HashMap을 만들고 아무 값도 넣지 않으면 컴파일러가 키와 값 타입을 추론하지 못할 수 있습니다. HashMap<&str, u32>처럼 의도를 적으면 해결됩니다. 타입 추론에 정보가 충분한지 확인하세요.
연습과 해설
sessions에 ("Rust", 15)를 하나 더 추가하고 결과를 확인하세요. 기록은 5개, Rust는 75분, CLI는 30분이어야 합니다.
해설: sessions.push(("Rust", 15));를 집계 반복문 전에 추가합니다. 집계 후에 추가하면 이미 계산한 totals에는 반영되지 않습니다. 데이터 변경과 계산 순서도 함께 확인하세요.
다음 10편에서는 커진 코드를 모듈로 나누고, 공통 동작을 trait으로 표현한 뒤 외부 크레이트로 JSON 출력을 만들어 봅니다.
실행 확인
이 글의 핵심 실습은 Rust 1.99.1, 2024 Edition의 Linux 환경에서 확인했습니다. 의도적으로 실패하는 예제는 설명한 오류가 발생하는지 구분해 검사했습니다. 운영체제별 오류 문구는 다를 수 있습니다.



Discussion
No comments yet