HashMap은 컬렉션의 일종으로, HashMap<K, V> 타입은 K 타입의 키에 V 타입의 값을 매핑한 것을 저장합니다. 해쉬맵은 벡터를 이용하듯 인덱스를 이용하는 것이 아니라 임의의 타입으로 된 키를 이용하여 데이터를 찾기를 원할때 유용합니다.
1. Struct와 HashMap
항목 | Struct | HashMap |
---|---|---|
정의 방식 | 컴파일 타임에 고정된 필드와 타입 정의 | 런타임에 키-값 쌍으로 유동적으로 데이터 저장 |
키 | 고정된 필드 이름 | 임의의 키 (보통 String 등 Eq + Hash 트레잇 필요) |
타입 안정성 | 필드 타입이 컴파일 시점에 결정됨 | 모든 값이 동일 타입이거나 제네릭으로 지정됨 |
사용 목적 | 고정된 구조의 데이터 표현 | 동적으로 키/값 데이터를 저장하고 조회 |
※ 제네릭(Generic) : 어떤 자료형이든 사용할 수 있도록 유연하고 재사용 가능한 코드를 만드는 기능
2. HashMap 생성과 삽입
HashMap을 사용하려면 먼저 std::collections에서 가져와야 합니다.
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert("Blue", 10);
scores.insert("Red", 50);
}
HashMap::new()로 HashMap을 생성합니다.
let mut scores = HashMap::new();
는 가변형 scores란 변수에 빈 HashMap을 대입하는 것입니다. 다시 말하면 빈 HashMap을 가변형 scroes란 이름으로 만드는 것입니다.
insert(key, value)로 값을 추가할 수 있습니다.
scores.insert(“Blue”, 10);
는 scores는 HashMap의 키로 Blue(&str type), 값으로 10을 대입하는 것이고,
scores.insert(“Red”, 50);
는 scores는 HashMap의 키로 Red(&str type), 값으로 50을 대입하는 것입니다.
이때 HashMap은 키와 값의 타입을 모두 제네릭으로 받으며, 보통 자동 추론됩니다.
Blue와 Red는 &str 형식이고, 10과 50은 정수이므로 i32로 해쉬맵의 형식이 추론된 것입니다.

“Blue”와 “Red”를 String::from(“Blue”)와 String::from(“Red”)로 수정하면, Key의 type이 String으로 자동 변경됩니다.

첫번째는 String type이고, 두번째는 &str 타입이면 두번째 key 아래에 빨간색 물결 줄이 표시되는데, 커서를 갖다대면 mismatched types 에러가 표시됩니다.

3. 값 접근 및 소유권
값을 읽을 때는 get() 메서드를 사용합니다.
let team_name = "Blue";
let score = scores.get(team_name);
println!("{:?}", score); // Some(10)
get()은 Option<&V>를 반환하므로, Some(v) 또는 None으로 패턴 매칭하거나 unwrap_or 등을 활용해야 합니다.
match score {
Some(score) => println!("{team_name} 팀의 점수: {score}"),
None => println!("{team_name} 팀의 점수를 찾을 수 없습니다."),
}
println!(“{:?}”, score); 의 결과는 Some(10)인데, 위와 같이 match 흐름 제어 연산자를 사용하면 점수가 있으면 점수, 여기서는 10이 반환되고,

팀 이름을 이름이 없는 Green으로 변경하면 Option 값은 None이 출력되고, match 연산자의 결과는 “Green 팀의 점수를 찾을 수 없습니다.”란 메시지가 나오게 됩니다.

소유권 이슈
Rust의 HashMap은 키와 값의 소유권을 가져갑니다.
let field = String::from("Favorite color");
let value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field, value);
// println!("{}", field); // 오류! 소유권이 이동됨
따라서, 위 코드에서 println! 앞의 주석을 제거하고 실행하면
value borrowed here after move 에러가 발생합니다.
위 오류를 없애려면 map.insert안 의 field를 &field로 수정하면 됩니다.
4. for 루프로 순회하기
HashMap의 모든 값을 순회하려면 for 루프를 사용합니다.
for (key, value) in &scores {
println!("{}: {}", key, value);
}
참조로 순회하므로 키나 값을 수정하진 않지만, 읽을 수 있습니다.
5. 조건부 삽입: entry()와 or_insert()
entry()는 키의 존재여부에 따라 다른 동작을 하게 해주는 API입니다. entry 함수의 리턴값은 열거형 Entry인데, 해당 키가 있는지 혹은 없는지를 나타냅니다.
scores.entry("Yellow").or_insert(30);
scores.entry("Blue").or_insert(50);
- “Yellow”라는 key가 없기 때문에 value 30이 추가되고,
- “Blue”는 이미 존재하므로 기존 값(10)이 유지됩니다.
or_insert()는 존재하지 않을 때만 값을 삽입하는 매우 유용한 메소드입니다.
6. 값을 업데이트하기
HashMap의 값을 직접 변경하려면 get_mut() 또는 entry()를 활용할 수 있습니다.
기존 값 수정 (1) :
if let Some(score) = scores.get_mut("Red") {
*score += 10;
}
if let Some(score) = scores.get_mut(“Red”) {
: score가 Red의 score라면 score를 가변 참조로 반환하는데,
*score += 10;
: 참조를 반환하므로 값을 수정하기 위해서는 dereference(역참조, 포인터가 가리키는 메모리 주소에 저장된 값을 읽거나 쓰는 것)하는 연산자 *를 앞에 붙여야 하며, 이 구문은 score에 10을 더하는 것입니다.
기존 값 수정 (2) :
또는 entry() 기반으로도 가능:
scores.entry("Red").and_modify(|v| *v += 1);
Red라는 키가 있으면, value를 v로 받아서, 1을 더하는 것입니다.
7. 예제: 단어 개수 세기
HashMap은 문자열 데이터를 처리할 때 특히 유용합니다. 예를 들어, 문장에서 각 단어의 개수를 세어보겠습니다.
이 코드는 split_whitespace()로 공백 기준으로 단어를 나누고, 각 단어가 등장한 횟수를 계산합니다.
use std::collections::HashMap;
fn main() {
let text = "hello world hello rust";
let mut counts = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
println!("{:?}", counts);
}
*counts.entry(word).or_insert(0) += 1;
: word란 키가 있으면 value에 1을 더하고, 없으면 value에 0을 추가하는 것입니다.
출력 결과는 아래와 같은데
{“hello”: 2, “world”: 1, “rust”: 1}
key 순으로 정렬하지 않아 표시되는 순서가 매번 달라집니다.
9. HashMap 요약
- HashMap는 키-값 쌍을 저장하는 자료구조
- insert()로 값 추가, get()으로 값 조회
- 키는 고유해야 하며, 같은 키로 다시 삽입하면 덮어씀
- entry()와 or_insert()로 조건부 삽입 가능
- 순회는 for (k, v) in &map
- 소유권은 HashMap으로 이동됨
- 문자열 처리에 매우 유용함