Rust 08. Vec, String, &str, HashMap 기초
요약
Rust 기본 문법을 익힌 뒤 실제 코드를 쓰기 시작하면 가장 자주 마주치는 타입은 Vec, String, &str, HashMap이다. 이 네 가지를 이해하면 입력을 읽고, 문자열을 나누고, 결과를 누적하는 초반 실습이 훨씬 자연스러워진다.
이 글은 위 네 가지를 입문 기준으로 연결해 정리한다. 결론부터 말하면 여러 값을 순서대로 담을 때는 Vec, 소유하고 수정하는 문자열은 String, 빌려 읽는 문자열은 &str, 키-값 누적과 조회는 HashMap으로 이해하면 된다.
문서 정보
- 작성일: 2026-04-15
- 검증 기준일: 2026-04-16
- 문서 성격: tutorial
- 테스트 환경: Windows 11 Pro, Cargo 프로젝트, Windows PowerShell 예시 명령,
src/main.rs - 테스트 버전: rustc 1.94.0, cargo 1.94.0
- 출처 등급: 공식 문서만 사용했다.
- 비고: 대표 예제를 로컬에서 재실행했고,
HashMap순회 순서와 UTF-8 세부 처리는 입문 수준에서 필요한 범위만 다뤘다.
문제 정의
Rust 초급 단계에서 아래 네 가지는 자주 같이 등장하지만 처음에는 경계가 흐리기 쉽다.
- 여러 값을 순서대로 모을 때 어떤 컬렉션을 써야 하는지
- 문자열을 소유할지 빌릴지 어떻게 구분할지
- 문자열 길이와 슬라이스를 UTF-8 기준으로 어떻게 이해할지
- 단어 수나 설정 값처럼 키 기준 누적 결과를 어디에 저장할지
이 글은 위 질문을 하나의 흐름으로 연결한다. BTreeMap, Cow, entry() API 심화, UTF-8 내부 표현 전체는 다루지 않는다.
읽는 기준은 “순서가 필요한가”, “문자열을 소유해야 하는가”, “키로 찾아야 하는가”를 먼저 묻는 것이다. 이 글의 예제는 컬렉션 이름을 암기하기 위한 것이 아니라, 입력을 읽고 문자열을 나눈 뒤 결과를 누적하는 초급 프로그램의 기본 흐름을 보여 주기 위한 것이다.
확인된 사실
Vec<T>는 같은 타입 값을 순서대로 저장하는 growable collection이다. 근거: Storing Lists of Values with Vectors, Vec in std::vec 의미: 항목의 순서가 중요하고 개수가 늘어날 수 있으면Vec를 먼저 떠올리면 된다.String은 소유권이 있는 UTF-8 문자열이고,str은 primitive string slice 타입이며 보통&str형태로 빌려 사용된다. 근거: Storing UTF-8 Encoded Text with Strings, String in std::string, str primitive type 의미: 문자열을 저장하거나 수정해야 하면String, 이미 있는 문자열을 읽기만 하면&str로 시작하는 편이 자연스럽다.HashMap<K, V>는 키-값 연관 데이터를 저장하는 표준 컬렉션이다. 근거: Storing Keys with Associated Values in Hash Maps, HashMap in std::collections 의미: “이 단어가 몇 번 나왔는가”처럼 키로 값을 누적하거나 조회해야 하면HashMap이 기본 선택지다.Vec::get은Option<&T>를 반환하고,str::len()은 글자 수가 아니라 바이트 수를 반환한다. 근거: Vec in std::vec, str primitive type 의미: Rust는 실패 가능성이 있는 접근과 UTF-8 문자열 처리를 숨기지 않는다. 없는 인덱스는Option으로, 문자열 길이는 바이트와 문자 개수를 구분해서 다뤄야 한다.- 입문 실습 흐름은
cargo new프로젝트 기준으로 설명하는 편이 가장 재현하기 쉽다. 근거: Hello, Cargo! 의미: 컬렉션 예제는 작은 입력을 바꿔 가며 출력 변화를 보는 방식이 가장 이해하기 쉽다.
직접 재현한 결과
1. 새 Cargo 프로젝트에서 src/main.rs를 교체하며 반복 실행하는 방식이 가장 단순했다
- 직접 확인한 결과: 아래처럼 새 프로젝트를 만들고
cargo run을 반복하는 흐름이 가장 편했다.
cargo new rust-collections-basics
cd rust-collections-basics
code .
cargo run
2. Vec, String, &str, UTF-8 길이 차이는 짧은 예제로 바로 확인됐다
- 직접 확인한 결과: 아래 예제로 vector 접근, owned string과 borrowed string, 바이트 수와 문자 수 차이를 한 번에 확인할 수 있었다.
fn main() {
let mut scores: Vec<i32> = Vec::new();
scores.push(10);
scores.push(20);
scores.push(30);
println!("len = {}", scores.len());
println!("scores[0] = {}", scores[0]);
println!("index 10 = {:?}", scores.get(10));
let literal: &str = "Rust";
let mut owned = String::from("Rust");
owned.push_str(" language");
println!("literal = {}", literal);
println!("owned = {}", owned);
let text = "한글";
println!("bytes = {}", text.len());
println!("chars = {}", text.chars().count());
}
- 관찰된 결과:
len = 3
scores[0] = 10
index 10 = None
literal = Rust
owned = Rust language
bytes = 6
chars = 2
- 읽는 법:
scores[0]은 값이 있다고 가정하는 접근이고,scores.get(10)은 없을 수도 있음을None으로 드러낸다."한글"의 바이트 수가 6이고 문자 수가 2인 결과는 Rust 문자열이 UTF-8 바이트 위에 있다는 점을 보여 준다.
3. 단어 빈도 수 예제로 Vec, &str, String, HashMap 연결이 분명해졌다
- 직접 확인한 결과: 여러 문장을 합친 뒤 단어 빈도 수를 세는 흐름을 아래처럼 재현할 수 있었다.
use std::collections::HashMap;
fn count_words(text: &str) -> HashMap<String, usize> {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
let normalized = word.to_lowercase();
if let Some(count) = counts.get_mut(&normalized) {
*count += 1;
} else {
counts.insert(normalized, 1);
}
}
counts
}
fn main() {
let lines = vec![
"Rust makes systems programming safer",
"Rust makes concurrency easier to reason about",
"safer code starts with clear data ownership",
];
let joined = lines.join(" ");
let counts = count_words(&joined);
println!("rust = {:?}", counts.get("rust"));
println!("safer = {:?}", counts.get("safer"));
}
- 관찰된 결과:
rust = Some(2)
safer = Some(2)
- 읽는 법:
lines는 여러 문장을 순서대로 담는Vec,joined는 소유 문자열인String,count_words의 인자는 빌려 읽는&str,counts는 단어별 누적 결과를 담는HashMap이다. 이 예제는 네 타입이 따로 노는 것이 아니라 하나의 처리 흐름에서 이어진다는 점을 보여 준다.
해석 / 의견
- 이 단계에서 중요한 판단: 컬렉션 이름을 외우는 것이 아니라 “언제 소유하고, 언제 빌리고, 언제 누적 저장할 것인가”를 구분하는 일이다.
- 선택 기준: 읽기 전용 문자열 인자는
&str, 수정하거나 보관할 문자열은String, 순서 있는 목록은Vec, 키 기반 누적은HashMap으로 시작한다. - 해석:
HashMap은 결과 누적에는 편하지만 순회 순서를 보장하지 않으므로, 출력 순서가 중요한 상황은 별도로 구분해서 설명하는 편이 좋다.
한계와 예외
- 이 글은 초급자 기준의 가장 자주 쓰는 패턴만 다룬다.
BTreeMap,Cow,entry()API 심화는 범위 밖이다. HashMap순회 순서는 보장되지 않으므로 예제 출력의 마지막 부분은 실행마다 달라질 수 있다.- 문자열의
len()은 바이트 수이며, 모든 문자열 처리를 바이트 인덱스로 직접 다루는 것은 안전하지 않을 수 있다. - UTF-8 내부 표현 전체와 고급 문자열 최적화는 다루지 않았다.
- 이 글을 읽고도 남는 질문은
entry()API, 정렬 가능한 map 선택, 문자열 slice 인덱싱 전략이며, 이는 컬렉션과 문자열 심화에서 다루는 편이 좋다.
댓글남기기