Post

[RUST] 러스트 프로그래밍 공식 가이드(제2판) 8장 요약

일반적인 컬렉션

  • 컬렉션 : 여러 값을 한 데이터 구조에 담아 다룰 수 있는 표준 라이브러리 타입들의 모음
    • 벡터 : 여러 개의 값을 서로 붙어 있게 저장할 수 있도록 해줌
    • 문자열 : 문자의 모음
    • 해시 맵 : 어떤 값을 특정한 키와 연관지어주도록 해줌

벡터에 여러 값의 목록 저장하기

  • 벡터 타입 : Vec<T>
    • 메모리에서 모든 값을 서로 이웃하도록 배치하는 단일 데이터 구조
    • 하나 이상의 값을 저장할 수 있음
    • 같은 타입의 값만 저장할 수 있음
      • 여러 타입을 섞어 쓰고 싶은 경우 enum 또는 박스 같은 트릭을 사용해야 한다고 함

새 벡터 만들기

1
let v: Vec<i32> = Vec::new()
  • 비어 있는 새 벡터를 만들려면 Vec::new 함수를 호출
  • 벡터는 제네릭을 이용하여 구현됨
    • 제네릭이란 타입을 매개변수처럼 받아 여러 타입에 대해 재사용할 수 있게 해주는 기능을 말함
      • 위 에서 지정한 <i32>Veci32 타입의 요소를 갖음을 러스트에게 알려주는 것
      • 제네릭에 지정한 타입에 따라 어떠한 타입의 값이라도 저장할 수 있음
  • 초깃값이 없는 벡터를 생성 경우
    • 저장하고자 하는 요소가 어떤 타입인지 알지 못하므로 타입 명시가 필요
  • 초깃값이 있는 벡터를 생성 경우
    • 저장하고자 하는 값의 타입을 대부분 유추할 수 있으므로 타입 명시가 거의 불필요
      • 초깃값이 있지만 값의 타입이 유추가 안되는 경우 초깃값이 없는 벡터와 같이 타입 명시가 필요
  • 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과 동일한 작업을 수행
        • 그럼 무엇을 사용해야하는가? 에 대한 답은 스타일가독성의 문제

문자열 업데이트하기

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');
      
      • 위 코드를 실행한 결과 slol
      • push 메서드는 한 개의 글자를 매개변수로 받아서 String에 추가

+연산자나 format! 매크로를 이용한 접합

  • +연산자를 사용하여 두 String값을 하나의 새로운 String값으로 조합하기
    • 1
      2
      3
      
      let s1 = String::from("Hello, ");
      let s2 = String::from("world!");
      let s3 = s1 + &s2; // s1은 여기로 이동되어 더 이상 사용할 수 없음에 주의
      
      • 위 코드를 실행한 결과 s3Hello, world!
      • + 연산자는 단순 문법(syntax sugar)이고, 실제로는 std::ops::Add 트레이트의 add 메서드를 호출
        • fn add(self, s: &str) -> String {
          • 표준 라이브러리에는 add가 제네릭과 연관 타입을 사용하여 정의되어 있음
          • String에는 &str만 더할 수 있음
          • 시그니처에서 self의 소유권을 가져 가는데 이는 self& 를 갖지 않기 때문
      • 실제 동작 과정 (s1 + &s2)
        1. s1의 소유권이 이동(move) 됨 (이 구조체(포인터+len+cap)의 소유권을 add 함수가 가져감)
          • 스택의 s1 은 더이상 사용할 수 없음
          • 힙 메모리에 있던 "Hello, " 데이터는 그대로 살아있음
        2. s1의 버퍼(힙에 있는 “Hello, “)를 가져옴
        3. s2가 참조(&s2)로 들어오는데, &String은 &str강제(coerce) 될 수 있기 때문
          • add가 호출되면 러스트는 역참조 강제를 사용하는데 이것이 add 함수 내에서 사용되는 &s2&s2[..]로 바꿈 (역참조 강제는 15장에서 다룸)
        4. &s2의 내용 "world!"를 복사해서 s1의 버퍼 끝에 붙임
        5. 새로운 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의 부분에 접근하고자 하면 에러를 얻게 됨

내부적 표현

  • StringVec<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(아스키 코드 값)를 반환하게 됨
  • 예싱치 못한 값을 반환하고 즉시 발견되지 않을 수 있는 버그를 방지하기 위해서 러스트는 이러한 코드를 전혀 컴파일 하지 않고 방지하기 위해 문자열 인덱싱을 금지

바이트 스칼라값과 문자소 클러스터! 이런!

  • 러스트의 관점에서 문자열을 보는 세 가지 관련 방식
    1. 바이트(Byte)
      • String&strUTF-8 바이트 시퀀스(u8 배열)로 저장됨
    2. 유니코드 스칼라 값(Char)
      • Rust의 char는 유니코드 스칼라 값을 의미
    3. 문자소 클러스터(Grapheme Cluster)
      • 사람들이 “한 글자”라고 느끼는 단위 - 러스트는 컴퓨터가 저장하는 원시 문자열을 번역하는 다앙햔 방법을 제공
  • Rust가 인덱스로 접근하여 문자를 얻지 못하도록 하는 마지막 이유
    • 인덱스 연산이 언제나 상수 시간(O(1))에 실행될 것으로 기대 받기 때문
    • 문자열을 가지고 (O(1)) 성능을 보장하는 것은 불가능
      • 문자열 내에 유효한 문자가 몇 개 있는지 알아내기 위해 내용물을 시작 지점부터 인덱스로 지정된 곳 까지 훑어야 하기 때문

문자열 슬라이싱하기

  • [] 와 범위를 사용하여 특정 바이트들이 담고 있는 문자열 슬라이스를 생성할 수 있음
    • 1
      2
      
      let hello = "Здравствуйте";
      let s = &hello[0..4];
      
      • s는 문자열의 첫 4바이트를 담고 있는 &str가 됨
        • 즉, 위 글자들이 각각 2바이트를 차지하므로 s 의 값은 Зд가 됨
      • 만약 &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);
  • 표준 라이브러리의 컬렉션 부분으로 부터 HashMapuse로 가져옴
    • 사용 빈도가 비교적 낮으므로 프렐루드 기능에 포함되지 않음
    • 표준 라이브러리로부터 지원들 덜 받아 해시맵을 생성하는 기본 제공 매크로가 없음
  • 해시 맵을 생성하는 방법으로 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> 를 반환
        • 만일 이 해시 맵에 해당 키에 대한 값이 없다면 getNone 을 반환
      • copied 호출을 통해 Option<&i32>Option<i32> 로 얻어옴
      • unwrap_or 을 사용해 scores가 해당 키에 대한 아이템을 가지고 있지 않을 경우 score0을 설정하도록 처리
  • 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_namefield_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)에 데이터를 저장
  • HashMap은 해시 테이블과 관련된 서비스 거부 공격(Dos attack)에 저항 기능을 제공할 수 있는 SipHash라 불리는 해시함수를 사용
    • 가장 빠른 해시 알고리즘은 아니ㅎ지만, 성능이 다소 낮더라도 대신 보안이 더 강함
  • 기본 해시 함수가 목적에 사용되기엔 너무 느리다면, 다른 해셔 함수를 지정하여 다른 함수로 바꿀 수 있음
    • 해셔(hasher)BuildHasher 트레이트를 구현한 타입을 말함
  • 해셔 함수는 새로 구현해야 할 필요는 없고, crates,io에 수많은 범용적인 해시 알고리즘을 구현한 해셔를 제공하는 공유 라이브러리가 있음
This post is licensed under CC BY 4.0 by the author.