[RUST] 러스트 프로그래밍 공식 가이드(제2판) 8장 요약
일반적인 컬렉션
- 컬렉션 : 여러 값을 한 데이터 구조에 담아 다룰 수 있는 표준 라이브러리 타입들의 모음
- 벡터 : 여러 개의 값을 서로 붙어 있게 저장할 수 있도록 해줌
- 문자열 : 문자의 모음
- 해시 맵 : 어떤 값을 특정한 키와 연관지어주도록 해줌
벡터에 여러 값의 목록 저장하기
- 벡터 타입 :
Vec<T>
- 메모리에서 모든 값을 서로 이웃하도록 배치하는 단일 데이터 구조
- 하나 이상의 값을 저장할 수 있음
- 같은 타입의 값만 저장할 수 있음
- 여러 타입을 섞어 쓰고 싶은 경우
enum
또는박스
같은 트릭을 사용해야 한다고 함
- 여러 타입을 섞어 쓰고 싶은 경우
새 벡터 만들기
1
let v: Vec<i32> = Vec::new()
- 비어 있는 새 벡터를 만들려면
Vec::new
함수를 호출 - 벡터는 제네릭을 이용하여 구현됨
- 제네릭이란 타입을 매개변수처럼 받아 여러 타입에 대해 재사용할 수 있게 해주는 기능을 말함
- 위 에서 지정한
<i32>
는Vec
가i32
타입의 요소를 갖음을 러스트에게 알려주는 것 - 제네릭에 지정한 타입에 따라 어떠한 타입의 값이라도 저장할 수 있음
- 위 에서 지정한
- 제네릭이란 타입을 매개변수처럼 받아 여러 타입에 대해 재사용할 수 있게 해주는 기능을 말함
- 초깃값이 없는 벡터를 생성 경우
- 저장하고자 하는 요소가 어떤 타입인지 알지 못하므로 타입 명시가 필요
- 초깃값이 있는 벡터를 생성 경우
- 저장하고자 하는 값의 타입을 대부분 유추할 수 있으므로 타입 명시가 거의 불필요
- 초깃값이 있지만 값의 타입이 유추가 안되는 경우 초깃값이 없는 벡터와 같이 타입 명시가 필요
- 저장하고자 하는 값의 타입을 대부분 유추할 수 있으므로 타입 명시가 거의 불필요
vec!
매크로를 제공- 제공된 값들을 저장한 새로운
Vec
를 생성 1
let v = vec![1,2,3];
- 러스트는 벡터에 지정된 값을 보고 타입을 추론할 수 있으므로 타입 명시는 필요 없음
- 기본 정수형이
i32
이기 때문에 위 변수의 타입은Vec<i32>
- 기본 정수형이
- 러스트는 벡터에 지정된 값을 보고 타입을 추론할 수 있으므로 타입 명시는 필요 없음
- 제공된 값들을 저장한 새로운
벡터 업데이트하기
1
2
3
let mut v = Vec::new();
v.push(5);
v.push(6);
push
메서드를 사용하여 벡터에 요소를 추가- 벡터의 값을 변경하려면 가변 변수로 만들어야 함
Vec<i32>
타입을 명시하지 않아도 되는 이유- 집어 넣은 숫자가 모두
i32
타입인 점을 통해 러스트가v
의 타입을 추론
- 집어 넣은 숫자가 모두
벡터 요소 읽기
1
2
3
4
5
6
7
8
9
10
let v = vec![1,2,3,4,5];
let third: &i32 = &v[2];
println!("The third element is {third}");
let third: Option<&i32> = v.get(2);
match third {
Some(third) => println!("The third element is {third}"),
None => println!("There is no third element."),
}
- 벡터에 저장된 값을 참조하는 방법
- 인덱싱 (
v[i]
)- 벡터에서
i
번째 요소를 직접 반환 - 인덱스 범위를 벗어나면 panic 발생
1 2 3
thread 'main' panicked at main.rs:4:28: index out of bounds: the len is 5 but the index is 100 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
- 벡터에서
get
메서드 (v.get(i)
)- 벡터에서
i
번째 요소를Option<&T>
로 반환 - 인덱스 범위를 벗어나면
None
- 벡터에서
- 인덱싱 (
- 벡터 요소를 참조하는 방법을 두 가지 제공하는 이유
- 벡터에 없는 인덱스값을 사용하고자 했을 때 프로그램이 어떻게 작동할 것인지 선택할 수 있도록 하기 위함
- 유효한 참조자를 얻을 때, 빌림검사기가 소유권 및 빌림 규칙을 집행
- 빌림검사기
- 컴파일 시점에 소유권(ownership)과 빌림(borrowing) 규칙을 검사하는 컴파일러 내부 기능
- 즉, 누가 어떤 값을 읽고 쓰는지, 언제 값이 해제되는지를 체크해서 런타임 오류 없이 안전하게 참조할 수 있는지를 보장
1 2 3 4 5
let mut v = vec![1, 2, 3, 4, 5]; let first = &v[0]; // 불변 참조 v.push(6); // 재할당 발생 가능
- 댕글링 참조 발생으로 컴파일 에러가 발생
error[E0502]: cannot borrow 'v' as mutable because it is also borrowed as immutable
- 즉, 빌림검사기가 “first 참조자가 아직 살아있는데 push 같은 변경을 하면 위험하다”라고 판단해서 차단
- 벡터는 연속된 메모리 블록을 사용
- 새로운 요소를
push
할 때 남은 공간이 부족한 경우 (len == cap)- 더 큰 메모리 블록을 새로 할당
- 기존 요소들을 새 메모리로 복사
- 예전 메모리는 해제
- 새 메모리에 새 요소 추가
- 기존 요소에 대한 참조자
first
는 이미 해제 된 옛 메모리를 가리키게 됨 - 만약
len < cap
인 경우 (재할당이 일어나지 않는 경우) 에러가 발생하지 않을까?- 빌림 검사기는 “재할당이 실제로 일어나는지”는 알지 못함
- 즉, Vec의 내부 capacity 상태를 보지 않고, 단순히 “push가 메모리를 이동시킬 수 있는 가능성이 있다”는 이유로 불변 참조(
&v[0]
)와 가변 빌림(v.push
)을 동시에 허용하지 않음을 규칙으로 강제
- 댕글링 참조 발생으로 컴파일 에러가 발생
- 빌림검사기
벡터값에 대해 반복하기
- for 루프로 벡터의 요소들에 대해 반복하여 각 요소를 출력
1 2 3 4
let v = vec![100, 32 ,57]; for i in &v { println!("{i}"); }
벡터 요소에 대한 가변 참조로 반복
1 2 3 4
let mut v = vec![100, 32 ,57]; for i in &mut v { *i += 50; }
- 가변 참조자가 가리키는 값을 수정
for i in &mut v
- 루프 안에서
i
는 벡터 내부 요소의 가변 참조 (&mut i32
) 를 받게 됨
- 루프 안에서
*i
를 사용해야 하는 이유?i
자체는 가변 참조(&mut i32
)라서, 그냥i += 50
을 하면 “참조 자체에 더하기” 를 하는 의미가 되어 컴파일 에러가 발생- 역참조를 해서
i32
값을 꺼냄
- 가변 참조자가 가리키는 값을 수정
1
벡터에 대한 반복 처리는 불변이든 가변이든 상관없이 대여 검사 규칙에 의해 안전합니다.
열거형을 이용해 여러 타입 저장하기
1
2
3
4
5
6
7
8
9
10
11
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
]
- 열거형의 배리언트는 같은 열거형 타입에 정의되므로 열거형을 담을 벡터를 생성하여 사용
- 다양한 타입의 값을 갖는 배리언트를 보유한 열거형을 정의
- 모든 열거형 배리언트들은 해당 열거형 타입과 같은 타입으로 간주
- 러스트가 컴파일 타임에 벡터 내에 저장될 타입이 무엇인지 알아야 하는 이유
- 타입을 알아야 각 요소가 차지하는 크기 를 알 수 있기 때문
- 각 요소를 저장하기 위해 얼마만큼의 힙 메모리가 필요한지 알아야 하기 때문
- 만약 크기를 모른다면, 벡터가 메모리를 얼마나 잡아야 할지 몰라서 안전하게 요소를 저장할 수 없음
벡터가 버려지면 벡터의 요소도 버려집니다
1
2
3
4
{
let v = vec![1, 2, 3, 4];
// v 를 가지고 작업하기
} // <- 여기서 v 가 스코프 밖으로 벗어나고 해제됩니다.
struct
와 마찬가지로 벡터는 스코프를 벗어날 때 해제됨- 벡터가 버려질 때 벡터의 내용물도 전부 버려짐 (벡터가 가지고 있던 메모리가 정리됨)
- 대여 검사기는 벡터의 내용물에 대한 참조자의 사용이 해당 벡터가 유효할 때만 발생했는지 확인
문자열에 UTF-8 텍스트 저장하기
문자열이 뭔가요?
- 하나의 타입을 지칭하는 것이 아니라, 문자 데이터를 표현하는 방법 전체를 의미
- 문자열 (
String
)- 힙에 저장
- 표준 라이브러리를 통해 제공
- 가변(mutable) 가능
- 소유권(owner)을 가짐
- UTF-8로 인코딩 된 문자열 타입
- 런타임에 길이 조절 가능 (push…)
- 문자열 슬라이스 (
&str
)- 스택 또는 힙에 있는 문자열 일부를 참조
- 불변(immutable) 문자열
- 소유권 없음 (참조만 함)
- 문자열 리터럴은 기본적으로
&str
타입 - UTF-8 로 인코딩된 문자열 데이터를 가리킴
- 길이 조절 불가능 (고정)
- 문자열 (
새로운 문자열 생성하기
Vec<T>
에서 쓸 수 있는 연산 다수가 String
에서 똑같이 쓸 수 있는데, 이는 String
이 실제로 바이트 벡터에 더하여 몇가지 보장, 제한, 기능들을 추가한 래퍼(wrapper)로 구현되어 있기 때문입니다.
- 비어 있는 새로운
String
생성하기1
let mut s = String::new();
- 어떤 데이터를 담을 수 있는
s
라는 빈 문자열을 만들어줌
- 어떤 데이터를 담을 수 있는
to_string
메서드를 사용하여 문자열 리터럴로부터String
생성하기1 2 3 4
let data = "initial contents"; let s = data.to_string(); // 리터럴에서도 바로 작동 가능 let s = "initial contents".to_string();
- 저장해둘 문자열 초깃값을 가지고 있는 경우
to_string
메서드 이용 Display
트레이트가 구현된 어떤 타입으든 사용 가능Display
트레이트란 사람이 읽기 편한 형태로 데이터를 출력할 수 있도록 구현하는 표준 인터페이스- 즉, 숫자, 문자 등 여러 타입을 문자열로 바꿀 때도 to_string()을 쓸 수 있음
1 2
let x = 42; let s = x.to_string();
- 문자열 리터럴도 이 트레이트를 구현
1
let s = "initial contents".to_string();
- 저장해둘 문자열 초깃값을 가지고 있는 경우
String::from
함수를 사용하여 문자열 리터럴로부터 String 생성하기1
let s = String::from("initial contents");
- 위에서 봤던 to_string과 동일한 작업을 수행
- 그럼 무엇을 사용해야하는가? 에 대한 답은 스타일과 가독성의 문제
- 위에서 봤던 to_string과 동일한 작업을 수행
문자열 업데이트하기
push_str과 push를 이용하여 문자열 추가하기
push_str
메서드를 사용하여String
에 문자열 슬라이스 추가하기1 2
let mut s = String::from("foo"); s.push_str("bar");
- 위 코드를 실행한 결과
s
의 값은foobar
push_str
메서드는 문자열 슬라이스를 매겨변수로 갖는데 이는 매개변수의 소유권을 가져올 필요가 없기 때문
- 위 코드를 실행한 결과
push
를 사용하여String
값에 한 글자 추가하기1 2
let mut s = String::from("lo"); s.push('l');
- 위 코드를 실행한 결과
s
는lol
push
메서드는 한 개의 글자를 매개변수로 받아서String
에 추가
- 위 코드를 실행한 결과
+연산자나 format! 매크로를 이용한 접합
+
연산자를 사용하여 두String
값을 하나의 새로운String
값으로 조합하기1 2 3
let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // s1은 여기로 이동되어 더 이상 사용할 수 없음에 주의
- 위 코드를 실행한 결과
s3
는Hello, world!
+
연산자는 단순 문법(syntax sugar)이고, 실제로는std::ops::Add
트레이트의add
메서드를 호출fn add(self, s: &str) -> String {
- 표준 라이브러리에는
add
가 제네릭과 연관 타입을 사용하여 정의되어 있음 String
에는&str
만 더할 수 있음- 시그니처에서
self
의 소유권을 가져 가는데 이는self
가&
를 갖지 않기 때문
- 표준 라이브러리에는
- 실제 동작 과정 (
s1 + &s2
)s1
의 소유권이 이동(move) 됨 (이 구조체(포인터+len+cap)의 소유권을 add 함수가 가져감)- 스택의
s1
은 더이상 사용할 수 없음 - 힙 메모리에 있던
"Hello, "
데이터는 그대로 살아있음
- 스택의
s1
의 버퍼(힙에 있는 “Hello, “)를 가져옴s2
가 참조(&s2
)로 들어오는데,&String
은 &str
로 강제(coerce) 될 수 있기 때문add
가 호출되면 러스트는 역참조 강제를 사용하는데 이것이add
함수 내에서 사용되는&s2
를&s2[..]
로 바꿈 (역참조 강제는 15장에서 다룸)
&s2
의 내용"world!"
를 복사해서s1
의 버퍼 끝에 붙임- 새로운
String
객체를 만들어 반환하고, 그 소유권을s3
가 가짐
- 위 코드를 실행한 결과
- format! 사용
1 2 3 4 5
let s1 = String::from("tic"); let s2 = String::from("tac"); let s3 = String::from("toe"); let s = format!("{s1}-{s2}-{s3}"); // + 연산자의 경우 : s1 + "-" + &s2 + "-" + &s3;
format!
매크로는println!
처럼 작동하지만, 화면에 결과를 출력하는 대신 결과가 담긴String
을 반환+
연산자를 많아 확인이 어렵거나 복잡한 문자열 조합의 경우format!
을 사용- 가독성이 향상됨
- 참조자를 이용하므로 이 호출은 아무 매개변수의 소유권도 가져가지 않음
문자열 내부의 인덱싱
러스트는 인덱싱 문법을 이용하여 String
의 부분에 접근하고자 하면 에러를 얻게 됨
내부적 표현
String
은Vec<u8>
을 감싼 것으로, 실제로는 UTF-8 바이트 배열로 저장이 됨- “Hola” → 길이(len)는 4 (바이트도 4개, 글자도 4개).
- “Здравствуйте” → 길이(len)는 24 (글자는 12개지만, UTF-8에서 각 글자가 2바이트라 총 24바이트).
- 문자(char)와 바이트(byte)의 길이가 다름
- 한 글자가 반드시 1바이트가 아님 (UTF-8은 가변 길이 인코딩)
- 문자열에서 인덱스로 접근(
hello[0]
)할 수 없는 이유?- 인덱스 0은 실제로 첫 번째 바이트를 의미
- “З” 같은 글자는 2바이트(208, 151)로 표현되므로 hello[0]이 가리키는 값은 208일 뿐, 문자 “З”가 아님
- “hello”에서 &”hello”[0]도 ‘h’가 아닌 104(아스키 코드 값)를 반환하게 됨
- 예싱치 못한 값을 반환하고 즉시 발견되지 않을 수 있는 버그를 방지하기 위해서 러스트는 이러한 코드를 전혀 컴파일 하지 않고 방지하기 위해 문자열 인덱싱을 금지
바이트 스칼라값과 문자소 클러스터! 이런!
- 러스트의 관점에서 문자열을 보는 세 가지 관련 방식
- 바이트(Byte)
String
과&str
은 UTF-8 바이트 시퀀스(u8 배열)로 저장됨
- 유니코드 스칼라 값(Char)
- Rust의
char
는 유니코드 스칼라 값을 의미
- Rust의
- 문자소 클러스터(Grapheme Cluster)
- 사람들이 “한 글자”라고 느끼는 단위 - 러스트는 컴퓨터가 저장하는 원시 문자열을 번역하는 다앙햔 방법을 제공
- 바이트(Byte)
- Rust가 인덱스로 접근하여 문자를 얻지 못하도록 하는 마지막 이유
- 인덱스 연산이 언제나 상수 시간(
O(1)
)에 실행될 것으로 기대 받기 때문 - 문자열을 가지고 (
O(1)
) 성능을 보장하는 것은 불가능- 문자열 내에 유효한 문자가 몇 개 있는지 알아내기 위해 내용물을 시작 지점부터 인덱스로 지정된 곳 까지 훑어야 하기 때문
- 인덱스 연산이 언제나 상수 시간(
문자열 슬라이싱하기
- [] 와 범위를 사용하여 특정 바이트들이 담고 있는 문자열 슬라이스를 생성할 수 있음
1 2
let hello = "Здравствуйте"; let s = &hello[0..4];
s
는 문자열의 첫 4바이트를 담고 있는&str
가 됨- 즉, 위 글자들이 각각 2바이트를 차지하므로
s
의 값은Зд
가 됨
- 즉, 위 글자들이 각각 2바이트를 차지하므로
- 만약
&hello[0..1]
처럼 문자 바이트의 일부 슬라이스를 얻으려고 하는 경우- 러스트는 벡터 내에 유효하지 않은 인덱스에 접근했을 때와 동일한 방식으로 런타임에 패닉을 발생시킴
- 문자열 슬라이스를 생성하는 것은 프로그램을 죽게 만들 수 있기 때문에 주의 깊게 사용해야 함
문자열에 대한 반복을 위한 메서드
문자열 조각에 대한 연산을 하는 가장 좋은 방법은 명시적으로 문자를 원하는 것인지 바이트를 원하는 것인지 지정하는 것
- 개별적인 유니코드 스칼라값에 대해서는
chars
메서드를 사용1 2 3
for c in "Зд".chars() { println!("{c}"); }
- 출력
1 2
З д
- 출력
bytes
메서드는 각 원시 바이트를 반환(문제의 도메인이 무엇인가에 따라 적절)1 2 3
for b in "Зд".bytes() { println!("{b}"); }
- 출력
1 2 3 4
208 151 208 180
- 출력
문자열은 그렇게 단순하지 않습니다
- 러스트는 데이터의 올바른 처리를 모든 러스트 프로그램의 기본 동작으로 선택
- 이로인해 프로그래머가 UTF-8 데이터를 처리할 때 미리 더 많은 생각을 해야함을 의미
- 문자열의 복잡성을 더 많이 노출시키지만, 개발 생명주기 후반에 ASCII 아닌 문자와 관련된 에러를 처리해야 할 필요가 없도록 해줌
- 표준 라이브러리에 이런 복잡한 상황을 올바르게 처리하는데 도움이 될
String
및&str
타입 기반의 기능을 다양하게 제공
해시 맵에 서로 연관된 키와 값 저장하기
HashMap<K, V>
타입은 K
타입의 키와 V
타입의 값에 대해 해시 함수(hashing function)을 사용하여 매핑한 것을 저장
새로운 해시 맵 생성하기
1
2
3
4
5
6
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
- 표준 라이브러리의 컬렉션 부분으로 부터
HashMap
을use
로 가져옴- 사용 빈도가 비교적 낮으므로 프렐루드 기능에 포함되지 않음
- 표준 라이브러리로부터 지원들 덜 받아 해시맵을 생성하는 기본 제공 매크로가 없음
- 해시 맵을 생성하는 방법으로
new
를 사용 insert
를 이용하여 요소를 추가- 해시 맵은 데이터를 힙에 저장
- 해시맵의 모든 키는 서로 같은 타입이어야 하고, 모든 값도 동일 타입이어야 함
해시 맵의 값 접근하기
- 해시 맵 내에 저장된 블루 팀의 점수 접근하기
1 2 3 4 5 6 7 8 9
use std::collections::HashMap; let mut scores = HashMap::new(); score.insert(String::from("Blue"), 10); score.insert(String::from("Yellow"), 50); let team_name = String::from("Blue"); let score = scores.get(&team_name).copied().unwrap_or(0);
get
메서드에 키를 제공하여 해시 맵으로부터 값을 얻어올 수 있음get
메서드는Option<&V>
를 반환- 만일 이 해시 맵에 해당 키에 대한 값이 없다면
get
은None
을 반환
- 만일 이 해시 맵에 해당 키에 대한 값이 없다면
copied
호출을 통해Option<&i32>
를Option<i32>
로 얻어옴unwrap_or
을 사용해scores
가 해당 키에 대한 아이템을 가지고 있지 않을 경우score
에0
을 설정하도록 처리
- for 루프를 사용해 해시 맵 내의 키/값 상에 대한 반복 작업 수행
1 2 3
for (key, value) in &scores { println!("{key}: {value}"); }
- 출력
1 2
Yellow: 50 Blue: 10
- 출력
해시 맵과 소유권
1
2
3
4
5
6
7
8
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// field_name과 field_value는 이 시점부터 유효하지 않음
insert
를 호출하여field_name
과field_value
를 해시 맵으로 이동시킴i32
처럼Copy
트레이트를 구현한 타입의 값은 해시 맵 안으로 복사됨String
처럼 소유권이 있는 값의 경우, 값들이 이동되어 해시 맵이 그 값의 소유자가 됨
- 해시 맵에 값들의 참조자들을 삽입한다면, 이 값들은 해시맵으로 이동되지 않음
- 하지만, 참조자가 가리키고 있는 값은 해시 맵이 유효할 때까지 계속 유효해야 함
해시 맵 업데이트하기
해시맵 각각의 유일한 키는 연관된 값을 딱 하나만 가질 수 있습니다. (역은 성립 안됨)
값을 덮어쓰기
- 해시맵에 어떤 키와 값을 삽입하고, 그 후 같은 키에 다른 값을 삽입하면, 해당 키에 연관된 값은 새 값으로 대체됨 ```rust use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from(“Blue”), 10); scores.insert(String::from(“Blue”), 25);
println!(““,scores);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 출력 결과
- ```bash
{"Blue": 25}
```
#### 키가 없을 때만 키와 값 추가하기
- 해시맵의 `entry` 메서드
- 검사하려는 키를 매개변수로 받음
- 반환값은 열거형 `Entry`이며 해당 값이 있는지 없는지를 나타냄
- `Entry`의 `or_insert` 메서드
- 해당 키가 존재할 경우 `Entry` 키에 대한 연관된 값을 반환
- 해당 키가 존재하지 않을 경우 매개변수로 제공된 값을 해당 키에 대한 새 값으로 삽입하고 수정된 `Entry`에 대한 값을 반환
```rust
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
- 출력 결과
1
{"Yellow": 50, "Blue": 10}
예전 값에 기초하여 값을 업데이트하기
1
2
3
4
5
6
7
8
9
10
11
12
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
split_whitespace
메서드text
의 값을 공백 문자로 나눈 서브 슬라이스에 대한 반복자를 반환
or_insert
메서드- 실제로는 해당 키에 대한 값을 가변 참조자(&mut V)로 반환
count
변수에 가변 참조자를 저장하고, 여기에 값을 할당하기 위해 먼저 별표(*
)를 사용하여count
를 역참조 해야 함- 가변 참조자는
for
루프의 끝에서 스코프 밖으로 벗어나고, 따라서 모든 값의 변경은 안전하며 대여 규칙에 위배되지 않음 - 출력 결과
1
{"wonderful": 1, "world": 2, "hello": 1}
- 키/값 쌍의 출력 순서는 다를 수 있음
- 해시 맵에 대한 반복 처리는 임의의 순서로 일어남
해시 함수
- 해시함수란
- 키(key)를 고정 길이의 숫자(hash)로 변환하는 함수
- 목적: 키를 빠르게 검색, 삽입, 삭제 할 수 있도록 메모리 위치를 결정
- 예시
1 2
key: "Alice" → hash: 0x3f2a1c key: "Bob" → hash: 0x7b5d9e
- 이 해시 값에 따라 내부 버킷(bucket)에 데이터를 저장
- 키(key)를 고정 길이의 숫자(hash)로 변환하는 함수
HashMap
은 해시 테이블과 관련된 서비스 거부 공격(Dos attack)에 저항 기능을 제공할 수 있는SipHash
라 불리는 해시함수를 사용- 가장 빠른 해시 알고리즘은 아니ㅎ지만, 성능이 다소 낮더라도 대신 보안이 더 강함
- 기본 해시 함수가 목적에 사용되기엔 너무 느리다면, 다른 해셔 함수를 지정하여 다른 함수로 바꿀 수 있음
- 해셔(hasher)는
BuildHasher
트레이트를 구현한 타입을 말함
- 해셔(hasher)는
- 해셔 함수는 새로 구현해야 할 필요는 없고, crates,io에 수많은 범용적인 해시 알고리즘을 구현한 해셔를 제공하는 공유 라이브러리가 있음
This post is licensed under CC BY 4.0 by the author.