[Rust 시리즈 10편] 컬렉션으로 데이터 모아 다루기

English version

구조체와 enum으로 데이터를 표현할 수 있게 되었다면, 이제 여러 개의 값을 한꺼번에 관리할 컬렉션이 필요합니다. Rust 표준 라이브러리에서 가장 자주 사용하는 컬렉션은 Vec<T>와 [[hashmap|HashMap<K, V>]]입니다. 이번 글에서는 학습 로그를 벡터와 해시맵에 담고, 반복 처리(iteration)로 요약을 만들어 봅니다.

Vec 기본기

Vec<T>는 크기가 가변적인 배열입니다. 힙에 데이터를 저장하고, 필요할 때 push, pop, iter 등을 사용할 수 있습니다.

fn main() {
    let mut topics = Vec::new();
    topics.push("ownership".to_string());
    topics.push("borrowing".to_string());
    topics.push("enum".to_string());

    for topic in &topics {
        println!("학습 예정: {}", topic);
    }
}

for topic in &topics는 벡터의 각 요소에 대한 불변 참조를 순회합니다. 동시에 가변 참조를 만들지 않는 이상 안전하게 반복할 수 있습니다.

벡터와 구조체 연결하기

학습 로그 여러 개를 벡터로 관리하면, 간단한 리포트를 만들 수 있습니다.

struct StudyLog {
    topic: String,
    finished: bool,
}

fn main() {
    let logs = vec![
        StudyLog { topic: "borrowing".into(), finished: true },
        StudyLog { topic: "structs".into(), finished: true },
        StudyLog { topic: "enum".into(), finished: false },
    ];

    let finished_count = logs.iter().filter(|log| log.finished).count();
    println!("완료: {} / 총 {}건", finished_count, logs.len());
}

iter()는 불변 참조 이터레이터를 반환합니다. iter_mut()는 가변 참조 이터레이터로, 각 요소를 수정할 때 사용합니다.

fn main() {
    let mut progress = vec![10, 40, 70];
    for percent in progress.iter_mut() {
        *percent += 5;
    }
    println!("업데이트된 진행률: {:?}", progress);
}

HashMap으로 요약 만들기

[[hashmap|HashMap<K, V>]]는 키-값 쌍을 저장합니다. 학습 항목별로 메모 개수를 기록하는 데 사용해 봅시다.

use std::collections::HashMap;

fn main() {
    let mut notes: HashMap<String, Vec<String>> = HashMap::new();
    notes.entry("borrowing".into()).or_default().push("불변 참조 여러 개 허용".into());
    notes.entry("borrowing".into()).or_default().push("가변 참조는 하나".into());
    notes.entry("enum".into()).or_default().push("match로 상태 분기".into());

    for (topic, memos) in &notes {
        println!("{}: {}개 메모", topic, memos.len());
    }
}

entry API는 키가 없으면 새 값을 만들고, 있으면 기존 값을 반환합니다. insert는 값을 바로 덮어쓸 때 간단하고, entry는 "없으면 만들고 있으면 이어서 수정"할 때 편합니다. or_defaultDefault 구현을 호출해 빈 벡터를 만들어 주므로, 매번 insert 여부를 확인하지 않아도 됩니다.

HashMap과 enum 결합하기

이전 편에서 만든 Progress enum을 해시맵 값으로 사용하면, 각 주제가 어디까지 진행됐는지 한눈에 볼 수 있습니다.

use std::collections::HashMap;

#[derive(Debug)]
enum Progress {
    Todo,
    Doing(u8),
    Done,
}

fn main() {
    let mut status: HashMap<String, Progress> = HashMap::new();
    status.insert("borrowing".into(), Progress::Done);
    status.insert("structs".into(), Progress::Doing(50));
    status.insert("collections".into(), Progress::Todo);

    for (topic, progress) in &status {
        match progress {
            Progress::Todo => println!("{}: 시작 전", topic),
            Progress::Doing(p) => println!("{}: {}% 진행", topic, p),
            Progress::Done => println!("{}: 완료", topic),
        }
    }
}

불변 참조 이터레이터를 사용했으므로, 루프 안에서 status를 수정할 수 없습니다. 필요하면 iter_mut()로 가변 참조를 받아 수정한 뒤, 루프가 끝난 후에만 다른 연산을 수행하세요.

이터레이션 기본 패턴

  1. for item in collection은 ownership을 이동시킵니다. 반복이 끝나면 원본을 다시 쓰지 못할 수 있습니다.
  2. for item in &collection 또는 collection.iter()는 불변 참조를 순회합니다.
  3. for item in &mut collection 또는 iter_mut()은 가변 참조를 순회합니다. 동시에 다른 참조를 만들 수 없다는 borrowing 규칙을 잊지 마세요.

짧게 기억하면 아래와 같습니다.

  • 빌려서 보기: &collection, iter()
  • 빌려서 수정하기: &mut collection, iter_mut()
  • 통째로 가져가기: collection

이터레이터는 체이닝 메서드(map, filter, collect)를 제공해, 앞으로 배울 클로저와도 자연스럽게 연결됩니다.

CodeSandbox로 이어서 실습하기

아래 샌드박스는 CodeSandbox의 Rust starter입니다. 이번 글의 핵심 코드를 src/main.rs에 옮기고, cargo checkcargo run 결과를 나란히 보면서 컴파일 메시지와 실행 출력을 비교해 보세요.

Live Practice

Rust Practice Sandbox

CodeSandbox

Run the starter project in CodeSandbox, compare it with the lesson code, and keep experimenting.

Rust startercargoterminal
  1. starter를 fork한 뒤 src/main.rs를 연다
  2. 본문 예제를 붙여 넣고 cargo check와 cargo run을 차례로 실행한다
  3. 타입, 값, 참조 흐름을 바꿔 컴파일 피드백과 출력 차이를 비교한다

Rust 실습은 브라우저 미리보기보다 터미널 피드백이 더 중요합니다. 여러 파일 구조나 추가 crate가 필요한 예제는 파일 배치를 조금 더 손봐야 할 수 있습니다.

손으로 따라하기

  • Vec<StudyLog>에서 완료된 항목만 새 벡터로 모으는 함수를 작성해 보세요.
  • HashMap을 사용해 주제별 메모 문자열을 슬라이스(&str)로 출력하는 함수를 작성해 보세요.
  • for 루프 대신 iter().enumerate()를 사용해 진행률 목록에 인덱스를 붙여 출력해 보세요.

마무리

컬렉션은 실전 프로그램에서 데이터를 실제로 다루는 무대입니다. VecHashMap은 대부분의 로직에서 기본 도구가 되며, 참조 규칙과 이터레이터 패턴을 지키면 대량의 데이터를 안전하게 순회하고 수정할 수 있습니다. 다음 편부터는 컬렉션을 기반으로 에러 처리와 모듈 구조를 추가해, 더 큰 프로젝트로 확장할 준비를 시작합니다.

💬 댓글

이 글에 대한 의견을 남겨주세요