|
1 |
| -use std::path::PathBuf; |
| 1 | +use std::{ |
| 2 | + collections::{BTreeMap, BTreeSet, HashMap, HashSet}, |
| 3 | + hash::{BuildHasher, Hash}, |
| 4 | + path::PathBuf, |
| 5 | +}; |
2 | 6 |
|
3 | 7 | use crate::Revision;
|
4 | 8 |
|
@@ -51,6 +55,8 @@ pub mod helper {
|
51 | 55 | ///
|
52 | 56 | /// Impl will fulfill the postconditions of `maybe_update`
|
53 | 57 | pub unsafe trait Fallback<T> {
|
| 58 | + /// # Safety |
| 59 | + /// |
54 | 60 | /// Same safety conditions as `Update::maybe_update`
|
55 | 61 | unsafe fn maybe_update(old_pointer: *mut T, new_value: T) -> bool;
|
56 | 62 | }
|
@@ -106,15 +112,21 @@ pub fn always_update<T>(
|
106 | 112 |
|
107 | 113 | /// # Safety
|
108 | 114 | ///
|
109 |
| -/// The `unsafe` on the trait is to assert that `maybe_update` ensures |
110 |
| -/// the properties it is intended to ensure. |
| 115 | +/// Implementing this trait requires the implementor to verify: |
| 116 | +/// |
| 117 | +/// * `maybe_update` ensures the properties it is intended to ensure. |
| 118 | +/// * If the value implements `Eq`, it is safe to compare an instance |
| 119 | +/// of the value from an older revision with one from the newer |
| 120 | +/// revision. If the value compares as equal, no update is needed to |
| 121 | +/// bring it into the newer revision. |
111 | 122 | ///
|
112 |
| -/// `Update` must NEVER be implemented for any `&'db T` value |
113 |
| -/// (i.e., any Rust reference tied to the database). |
114 |
| -/// This is because update methods are invoked across revisions. |
115 |
| -/// The `'db` lifetimes are only valid within a single revision. |
116 |
| -/// Therefore any use of that reference in a new revision will violate |
117 |
| -/// stacked borrows. |
| 123 | +/// NB: The second point implies that `Update` cannot be implemented for any |
| 124 | +/// `&'db T` -- (i.e., any Rust reference tied to the database). |
| 125 | +/// Such a value could refer to memory that was freed in some |
| 126 | +/// earlier revision. Even if the memory is still valid, it could also |
| 127 | +/// have been part of a tracked struct whose values were mutated, |
| 128 | +/// thus invalidating the `'db` lifetime (from a stacked borrows perspective). |
| 129 | +/// Either way, the `Eq` implementation would be invalid. |
118 | 130 | pub unsafe trait Update {
|
119 | 131 | /// # Returns
|
120 | 132 | ///
|
@@ -167,6 +179,98 @@ where
|
167 | 179 | }
|
168 | 180 | }
|
169 | 181 |
|
| 182 | +macro_rules! maybe_update_set { |
| 183 | + ($old_pointer: expr, $new_set: expr) => {{ |
| 184 | + let old_pointer = $old_pointer; |
| 185 | + let new_set = $new_set; |
| 186 | + |
| 187 | + let old_set: &mut Self = unsafe { &mut *old_pointer }; |
| 188 | + |
| 189 | + if *old_set == new_set { |
| 190 | + false |
| 191 | + } else { |
| 192 | + old_set.clear(); |
| 193 | + old_set.extend(new_set); |
| 194 | + return true; |
| 195 | + } |
| 196 | + }}; |
| 197 | +} |
| 198 | + |
| 199 | +unsafe impl<K, S> Update for HashSet<K, S> |
| 200 | +where |
| 201 | + K: Update + Eq + Hash, |
| 202 | + S: BuildHasher, |
| 203 | +{ |
| 204 | + unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool { |
| 205 | + maybe_update_set!(old_pointer, new_set) |
| 206 | + } |
| 207 | +} |
| 208 | + |
| 209 | +unsafe impl<K> Update for BTreeSet<K> |
| 210 | +where |
| 211 | + K: Update + Eq + Ord, |
| 212 | +{ |
| 213 | + unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool { |
| 214 | + maybe_update_set!(old_pointer, new_set) |
| 215 | + } |
| 216 | +} |
| 217 | + |
| 218 | +// Duck typing FTW, it was too annoying to make a proper function out of this. |
| 219 | +macro_rules! maybe_update_map { |
| 220 | + ($old_pointer: expr, $new_map: expr) => { |
| 221 | + 'function: { |
| 222 | + let old_pointer = $old_pointer; |
| 223 | + let new_map = $new_map; |
| 224 | + let old_map: &mut Self = unsafe { &mut *old_pointer }; |
| 225 | + |
| 226 | + // To be considered "equal", the set of keys |
| 227 | + // must be the same between the two maps. |
| 228 | + let same_keys = |
| 229 | + old_map.len() == new_map.len() && old_map.keys().all(|k| new_map.contains_key(k)); |
| 230 | + |
| 231 | + // If the set of keys has changed, then just pull in the new values |
| 232 | + // from new_map and discard the old ones. |
| 233 | + if !same_keys { |
| 234 | + old_map.clear(); |
| 235 | + old_map.extend(new_map); |
| 236 | + break 'function true; |
| 237 | + } |
| 238 | + |
| 239 | + // Otherwise, recursively descend to the values. |
| 240 | + // We do not invoke `K::update` because we assume |
| 241 | + // that if the values are `Eq` they must not need |
| 242 | + // updating (see the trait criteria). |
| 243 | + let mut changed = false; |
| 244 | + for (key, new_value) in new_map.into_iter() { |
| 245 | + let old_value = old_map.get_mut(&key).unwrap(); |
| 246 | + changed |= V::maybe_update(old_value, new_value); |
| 247 | + } |
| 248 | + changed |
| 249 | + } |
| 250 | + }; |
| 251 | +} |
| 252 | + |
| 253 | +unsafe impl<K, V, S> Update for HashMap<K, V, S> |
| 254 | +where |
| 255 | + K: Update + Eq + Hash, |
| 256 | + V: Update, |
| 257 | + S: BuildHasher, |
| 258 | +{ |
| 259 | + unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool { |
| 260 | + maybe_update_map!(old_pointer, new_map) |
| 261 | + } |
| 262 | +} |
| 263 | + |
| 264 | +unsafe impl<K, V> Update for BTreeMap<K, V> |
| 265 | +where |
| 266 | + K: Update + Eq + Ord, |
| 267 | + V: Update, |
| 268 | +{ |
| 269 | + unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool { |
| 270 | + maybe_update_map!(old_pointer, new_map) |
| 271 | + } |
| 272 | +} |
| 273 | + |
170 | 274 | unsafe impl<T> Update for Box<T>
|
171 | 275 | where
|
172 | 276 | T: Update,
|
|
0 commit comments