iddqd/bi_hash_map/ref_mut.rs
1use crate::{BiHashItem, DefaultHashBuilder, support::map_hash::MapHash};
2use core::{
3 fmt,
4 hash::BuildHasher,
5 ops::{Deref, DerefMut},
6};
7
8/// A mutable reference to a [`BiHashMap`] item.
9///
10/// This is a wrapper around a `&mut T` that panics when dropped, if the
11/// borrowed value's keys have changed since the wrapper was created.
12///
13/// # Change detection
14///
15/// It is illegal to change the keys of a borrowed `&mut T`. `RefMut` attempts
16/// to enforce this invariant.
17///
18/// `RefMut` stores the `Hash` output of keys at creation time, and recomputes
19/// these hashes when it is dropped or when [`Self::into_ref`] is called. If a
20/// key changes, there's a small but non-negligible chance that its hash value
21/// stays the same[^collision-chance]. In that case, as long as the new key is
22/// not the same as another existing one, internal invariants are not violated
23/// and the [`BiHashMap`] will continue to work correctly. (But don't do this!)
24///
25/// It is also possible to deliberately write pathological `Hash`
26/// implementations that collide more often. (Don't do this either.)
27///
28/// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is
29/// called on it. If a key is changed and `mem::forget` is then called on the
30/// `RefMut`, the `BiHashMap` will stop functioning correctly. This will not
31/// introduce memory safety issues, however.
32///
33/// The issues here are similar to using interior mutability (e.g. `RefCell` or
34/// `Mutex`) to mutate keys in a regular `HashMap`.
35///
36/// [`mem::forget`]: std::mem::forget
37///
38/// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability
39/// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday
40/// problem], the probability of a collision by chance reaches 10⁻⁶ within
41/// around 6 × 10⁶ elements.
42///
43/// [`BiHashMap`]: crate::BiHashMap
44/// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
45pub struct RefMut<
46 'a,
47 T: BiHashItem,
48 S: Clone + BuildHasher = DefaultHashBuilder,
49> {
50 inner: Option<RefMutInner<'a, T, S>>,
51}
52
53impl<'a, T: BiHashItem, S: Clone + BuildHasher> RefMut<'a, T, S> {
54 pub(super) fn new(hashes: [MapHash<S>; 2], borrowed: &'a mut T) -> Self {
55 Self { inner: Some(RefMutInner { hashes, borrowed }) }
56 }
57
58 /// Borrows self into a shorter-lived `RefMut`.
59 ///
60 /// This `RefMut` will also check hash equality on drop.
61 pub fn reborrow(&mut self) -> RefMut<'_, T, S> {
62 let inner = self.inner.as_mut().unwrap();
63 let borrowed = &mut *inner.borrowed;
64 RefMut::new(inner.hashes.clone(), borrowed)
65 }
66
67 /// Converts this `RefMut` into a `&'a T`.
68 pub fn into_ref(mut self) -> &'a T {
69 let inner = self.inner.take().unwrap();
70 inner.into_ref()
71 }
72}
73
74impl<T: BiHashItem, S: Clone + BuildHasher> Drop for RefMut<'_, T, S> {
75 fn drop(&mut self) {
76 if let Some(inner) = self.inner.take() {
77 inner.into_ref();
78 }
79 }
80}
81
82impl<T: BiHashItem, S: Clone + BuildHasher> Deref for RefMut<'_, T, S> {
83 type Target = T;
84
85 fn deref(&self) -> &Self::Target {
86 self.inner.as_ref().unwrap().borrowed
87 }
88}
89
90impl<T: BiHashItem, S: Clone + BuildHasher> DerefMut for RefMut<'_, T, S> {
91 fn deref_mut(&mut self) -> &mut Self::Target {
92 self.inner.as_mut().unwrap().borrowed
93 }
94}
95
96impl<T: BiHashItem + fmt::Debug, S: Clone + BuildHasher> fmt::Debug
97 for RefMut<'_, T, S>
98{
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match self.inner {
101 Some(ref inner) => inner.fmt(f),
102 None => {
103 f.debug_struct("RefMut").field("borrowed", &"missing").finish()
104 }
105 }
106 }
107}
108
109struct RefMutInner<'a, T: BiHashItem, S> {
110 hashes: [MapHash<S>; 2],
111 borrowed: &'a mut T,
112}
113
114impl<'a, T: BiHashItem, S: BuildHasher> RefMutInner<'a, T, S> {
115 fn into_ref(self) -> &'a T {
116 if !self.hashes[0].is_same_hash(self.borrowed.key1()) {
117 panic!("key1 changed during RefMut borrow");
118 }
119 if !self.hashes[1].is_same_hash(self.borrowed.key2()) {
120 panic!("key2 changed during RefMut borrow");
121 }
122
123 self.borrowed
124 }
125}
126
127impl<T: BiHashItem + fmt::Debug, S> fmt::Debug for RefMutInner<'_, T, S> {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 self.borrowed.fmt(f)
130 }
131}