사용자 정의 구조체를 HashMap 또는 HashSet과 같은 컬렉션에서 사용하기 위해서는 Eq 및 Hash 트레이트(그리고 자주 PartialEq도) 를 구현해야 합니다. 이러한 트레이트를 구현하는 것은 객체가 서로 비교되는 방식과 해시가 계산되는 방식을 정의합니다.
중요: 두 객체가 같다면 (a == b), 그들의 해시 값은 일치해야 합니다 (hash(a) == hash(b)). 그렇지 않으면 해시에 의존하는 컨테이너가 올바르게 작동하지 않으며(요소를 찾거나 삭제할 수 없음), 오류가 발생합니다.
구현 예:
use std::hash::{Hash, Hasher}; #[derive(Eq, PartialEq)] struct Point { x: i32, y: i32, } impl Hash for Point { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); self.y.hash(state); } }
Equal 비교(Eq/PartialEq)에 참여하는 필드와 해시에만 참여하는 필드를 포함하는 구조체에 대해 Hash를 구현할 수 있습니까?
답변:
아니요, 이것은 불변성을 위반하게 됩니다: 두 요소가 같다고 간주되는 경우 그들의 해시는 반드시 같아야 합니다. PartialEq/Eq에 참여하는 모든 필드는 Hash에서도 고려되어야 합니다. "비교되지 않는" 필드를 무시하는 것은 안전하지만, 그것들이 비교 논리에 참여하지 않는 경우에 한합니다.
impl Hash for MyType { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); // self.x가 Eq에서도 사용되는 경우에만 } }
이야기
대규모 서버 프로젝트에서 키 구조체가
HashMap에 사용되었지만 해시 계산에서PartialEq에 참여하는 필드를 잊어버렸습니다. 그 결과, 필요한 키를 찾을 수 없게 되어 캐시에서 사용자 "유출" 문제가 발생했습니다: 요소는 같다고 간주되었지만 실제로는 달랐습니다.
이야기
오픈 소스 라이브러리에서 기하학적 객체의
Hash및Eq에서는 부동 소수점 비교(f64)를 사용했습니다. 그 결과 NaN, -0.0/+0.0의 비교 특성으로 인해 표준 컨테이너가 잘못 작동하여 때때로 컬렉션에서 존재하는 객체를 찾지 못했습니다.
이야기
한 핀테크 프로젝트에서 리팩토링 시 비공개 필드를 포함한 구조체에 대해 자동 파생
#[derive(Hash, Eq, PartialEq)]를 사용하고, 선언 순서를 변경했습니다. 그 결과 최종 해시가 변경되어 캐시 기능이 파손되고 재시작 후 데이터 손실이 발생했습니다.