iddqd/id_ord_map/ref_mut.rs
1use super::IdOrdItem;
2use crate::support::map_hash::MapHash;
3use core::{
4 fmt,
5 hash::Hash,
6 ops::{Deref, DerefMut},
7};
8
9/// A mutable reference to an [`IdOrdMap`] entry.
10///
11/// This is a wrapper around a `&mut T` that panics when dropped, if the
12/// borrowed value's key has changed since the wrapper was created.
13///
14/// # Change detection
15///
16/// It is illegal to change the keys of a borrowed `&mut T`. `RefMut` attempts
17/// to enforce this invariant, and as part of that, it requires that the key
18/// type implement [`Hash`].
19///
20/// `RefMut` stores the `Hash` output of keys at creation time, and recomputes
21/// these hashes when it is dropped or when [`Self::into_ref`] is called. If a
22/// key changes, there's a small but non-negligible chance that its hash value
23/// stays the same[^collision-chance]. In that case, the map will no longer
24/// function correctly and might panic on access. This will not introduce memory
25/// safety issues, however.
26///
27/// It is also possible to deliberately write pathological `Hash`
28/// implementations that collide more often. (Don't do this.)
29///
30/// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is
31/// called on it. If a key is changed and `mem::forget` is then called on the
32/// `RefMut`, the [`IdOrdMap`] will no longer function correctly and might panic
33/// on access. This will not introduce memory safety issues, however.
34///
35/// The issues here are similar to using interior mutability (e.g. `RefCell` or
36/// `Mutex`) to mutate keys in a regular `HashMap`.
37///
38/// [`mem::forget`]: std::mem::forget
39///
40/// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability
41/// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday
42/// problem], the probability of a collision by chance reaches 10⁻⁶ within
43/// around 6 × 10⁶ elements.
44///
45/// [`IdOrdMap`]: crate::IdOrdMap
46/// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
47pub struct RefMut<'a, T: IdOrdItem>
48where
49 T::Key<'a>: Hash,
50{
51 inner: Option<RefMutInner<'a, T>>,
52}
53
54impl<'a, T: IdOrdItem> RefMut<'a, T>
55where
56 T::Key<'a>: Hash,
57{
58 pub(super) fn new(
59 state: foldhash::fast::FixedState,
60 hash: MapHash,
61 borrowed: &'a mut T,
62 ) -> Self {
63 let inner = RefMutInner { state, hash, borrowed };
64 Self { inner: Some(inner) }
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<'a, T: for<'k> IdOrdItemMut<'k>> RefMut<'a, T> {
75 /// Borrows self into a shorter-lived `RefMut`.
76 ///
77 /// This `RefMut` will also check hash equality on drop.
78 pub fn reborrow<'b>(&'b mut self) -> RefMut<'b, T> {
79 let inner = self.inner.as_mut().unwrap();
80 let borrowed = &mut *inner.borrowed;
81 RefMut::new(inner.state.clone(), inner.hash.clone(), borrowed)
82 }
83}
84
85impl<'a, T: IdOrdItem> Drop for RefMut<'a, T>
86where
87 T::Key<'a>: Hash,
88{
89 fn drop(&mut self) {
90 if let Some(inner) = self.inner.take() {
91 inner.into_ref();
92 }
93 }
94}
95
96impl<'a, T: IdOrdItem> Deref for RefMut<'a, T>
97where
98 T::Key<'a>: Hash,
99{
100 type Target = T;
101
102 fn deref(&self) -> &Self::Target {
103 self.inner.as_ref().unwrap().borrowed
104 }
105}
106
107impl<'a, T: IdOrdItem> DerefMut for RefMut<'a, T>
108where
109 T::Key<'a>: Hash,
110{
111 fn deref_mut(&mut self) -> &mut Self::Target {
112 self.inner.as_mut().unwrap().borrowed
113 }
114}
115
116impl<'a, T: IdOrdItem + fmt::Debug> fmt::Debug for RefMut<'a, T>
117where
118 T::Key<'a>: Hash,
119{
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 match self.inner {
122 Some(ref inner) => inner.fmt(f),
123 None => {
124 f.debug_struct("RefMut").field("borrowed", &"missing").finish()
125 }
126 }
127 }
128}
129
130struct RefMutInner<'a, T: IdOrdItem> {
131 state: foldhash::fast::FixedState,
132 hash: MapHash,
133 borrowed: &'a mut T,
134}
135
136impl<'a, T: IdOrdItem> RefMutInner<'a, T>
137where
138 T::Key<'a>: Hash,
139{
140 fn into_ref(self) -> &'a T {
141 let key: T::Key<'_> = self.borrowed.key();
142 // SAFETY: The key is borrowed, then dropped immediately. T is valid for
143 // 'a so T::Key is valid for 'a.
144 let key: T::Key<'a> =
145 unsafe { std::mem::transmute::<T::Key<'_>, T::Key<'a>>(key) };
146 if !self.hash.is_same_hash(&self.state, &key) {
147 panic!("key changed during RefMut borrow");
148 }
149
150 self.borrowed
151 }
152}
153
154impl<T: IdOrdItem + fmt::Debug> fmt::Debug for RefMutInner<'_, T> {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 self.borrowed.fmt(f)
157 }
158}
159
160/// A trait for mutable access to items in an [`IdOrdMap`].
161///
162/// This is a non-public trait used to work around a Rust borrow checker
163/// limitation. [This will produce a documentation warning if it becomes
164/// public].
165///
166/// This is automatically implemented whenever `T::Key` implements [`Hash`].
167///
168/// [`IdOrdMap`]: crate::IdOrdMap
169pub trait IdOrdItemMut<'a>: IdOrdItem<Key<'a>: Hash> + 'a {}
170
171impl<'a, T> IdOrdItemMut<'a> for T where T: 'a + IdOrdItem<Key<'a>: Hash> {}