├── .gitignore ├── Cargo.toml ├── README.md ├── benches └── basic.rs └── src ├── lib.rs └── trace.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "broom" 3 | version = "0.3.2" 4 | description = "An ergonomic tracing garbage collector that supports mark 'n sweep garbage collection" 5 | categories = ["data-structures", "memory-management", "algorithms"] 6 | keywords = ["garbage", "collection", "gc", "mark", "sweep"] 7 | repository = "https://github.com/zesterer/broom" 8 | authors = ["Joshua Barretto "] 9 | edition = "2018" 10 | license = "Apache-2.0/MIT" 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | hashbrown = "0.7" 15 | slotmap = "1.0" 16 | 17 | [dev-dependencies] 18 | rand = "0.7" 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Broom 2 | 3 | An ergonomic tracing garbage collector that supports mark 'n sweep garbage collection. 4 | 5 | [![Cargo](https://img.shields.io/crates/v/broom.svg)]( 6 | https://crates.io/crates/broom) 7 | [![Documentation](https://docs.rs/broom/badge.svg)]( 8 | https://docs.rs/broom) 9 | [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)]( 10 | https://github.com/zesterer/broom) 11 | 12 | ## Features 13 | 14 | - Ergonomic API 15 | - Mark and sweep heap cleaning 16 | - Easy (and safe) mutation of heap values, despite cycles 17 | - Zero-cost access to heap objects through handles 18 | 19 | ## Example 20 | 21 | ```rust 22 | use broom::prelude::*; 23 | 24 | // The type you want the heap to contain 25 | pub enum Object { 26 | Num(f64), 27 | List(Vec>), 28 | } 29 | 30 | // Tell the garbage collector how to explore a graph of this object 31 | impl Trace for Object { 32 | fn trace(&self, tracer: &mut Tracer) { 33 | match self { 34 | Object::Num(_) => {}, 35 | Object::List(objects) => objects.trace(tracer), 36 | } 37 | } 38 | } 39 | 40 | // Create a new heap 41 | let mut heap = Heap::default(); 42 | 43 | // Temporary objects are cheaper than rooted objects, but don't survive heap cleans 44 | let a = heap.insert_temp(Object::Num(42.0)); 45 | let b = heap.insert_temp(Object::Num(1337.0)); 46 | 47 | // Turn the numbers into a rooted list 48 | let c = heap.insert(Object::List(vec![a, b])); 49 | 50 | // Change one of the numbers - this is safe, even if the object is self-referential! 51 | *heap.get_mut(a).unwrap() = Object::Num(256.0); 52 | 53 | // Create another number object 54 | let d = heap.insert_temp(Object::Num(0.0)); 55 | 56 | // Clean up unused heap objects 57 | heap.clean(); 58 | 59 | // a, b and c are all kept alive because c is rooted and a and b are its children 60 | assert!(heap.contains(a)); 61 | assert!(heap.contains(b)); 62 | assert!(heap.contains(c)); 63 | 64 | // Because `d` was temporary and unused, it did not survive the heap clean 65 | assert!(!heap.contains(d)); 66 | ``` 67 | 68 | ## Who this crate is for 69 | 70 | - People writing dynamically-typed languages in Rust that want a simple, reliable garbage collector 71 | - People that want to have complex graph data structures with mutation and cycles but who don't want memory leaks 72 | 73 | ## Who this crate is not for 74 | 75 | - People that want garbage collection when writing ordinary Rust code 76 | 77 | ## Performance 78 | 79 | This crate makes no specific promises about performance. It is designed with a 'best attempt' approach; 80 | this means that it should be fast enough for most purposes but is probably not competitive with garbage 81 | collectors that have had years of development work ploughed into them. 82 | 83 | ## TODO 84 | 85 | There are a few things I want to do with `broom` if I get the time: 86 | 87 | - Smarter cleanup strategies than mark 'n sweep 88 | - Partial cleans to prevent garbage collection lag spikes 89 | 90 | If you're interested in working on any of these things, feel free to open a pull request! 91 | 92 | ## License 93 | 94 | Broom is licensed under either of: 95 | 96 | - Apache License 2.0, (http://www.apache.org/licenses/LICENSE-2.0) 97 | 98 | - MIT license (http://opensource.org/licenses/MIT) 99 | -------------------------------------------------------------------------------- /benches/basic.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use test::{Bencher, black_box}; 6 | use rand::prelude::*; 7 | use broom::prelude::*; 8 | 9 | pub enum Value { 10 | Root, 11 | Two(Handle, Handle), 12 | } 13 | 14 | impl Trace for Value { 15 | fn trace(&self, tracer: &mut Tracer) { 16 | if let Value::Two(a, b) = self { 17 | a.trace(tracer); 18 | b.trace(tracer); 19 | } 20 | } 21 | } 22 | 23 | fn make_complex_heap() -> (Heap, Handle) { 24 | let mut heap = Heap::default(); 25 | 26 | let mut root = heap.insert_temp(Value::Root); 27 | let mut items = Vec::new(); 28 | for _ in 1..10000 { 29 | items.push(root); 30 | let a = items.choose(&mut thread_rng()).unwrap(); 31 | root = heap.insert_temp(Value::Two(*a, root)); 32 | } 33 | 34 | (heap, root) 35 | } 36 | 37 | #[bench] 38 | fn insert(b: &mut Bencher) { 39 | let mut heap = Heap::default(); 40 | 41 | b.iter(|| heap.insert(Value::Root)); 42 | } 43 | 44 | #[bench] 45 | fn insert_temp(b: &mut Bencher) { 46 | let mut heap = Heap::default(); 47 | 48 | b.iter(|| heap.insert_temp(Value::Root)); 49 | } 50 | 51 | #[bench] 52 | fn fill_basic(b: &mut Bencher) { 53 | b.iter(|| { 54 | let mut heap = Heap::default(); 55 | for _ in 0..10000 { 56 | heap.insert_temp(Value::Root); 57 | } 58 | black_box(heap) 59 | }); 60 | } 61 | 62 | #[bench] 63 | fn fill_complex(b: &mut Bencher) { 64 | b.iter(|| black_box(make_complex_heap())); 65 | } 66 | 67 | #[bench] 68 | fn fill_and_clean(b: &mut Bencher) { 69 | b.iter(|| { 70 | let (mut heap, _) = make_complex_heap(); 71 | heap.clean(); 72 | assert_eq!(heap.len(), 0); 73 | }); 74 | } 75 | 76 | #[bench] 77 | fn fill_and_try_clean(b: &mut Bencher) { 78 | b.iter(|| { 79 | let (mut heap, root) = make_complex_heap(); 80 | let len = heap.len(); 81 | heap.clean_excluding(Some(root)); 82 | assert_eq!(heap.len(), len); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Broom 2 | //! 3 | //! An ergonomic tracing garbage collector that supports mark 'n sweep garbage collection. 4 | //! 5 | //! ## Example 6 | //! 7 | //! ``` 8 | //! use broom::prelude::*; 9 | //! 10 | //! // The type you want the heap to contain 11 | //! pub enum Object { 12 | //! Num(f64), 13 | //! List(Vec>), 14 | //! } 15 | //! 16 | //! // Tell the garbage collector how to explore a graph of this object 17 | //! impl Trace for Object { 18 | //! fn trace(&self, tracer: &mut Tracer) { 19 | //! match self { 20 | //! Object::Num(_) => {}, 21 | //! Object::List(objects) => objects.trace(tracer), 22 | //! } 23 | //! } 24 | //! } 25 | //! 26 | //! // Create a new heap 27 | //! let mut heap = Heap::default(); 28 | //! 29 | //! // Temporary objects are cheaper than rooted objects, but don't survive heap cleans 30 | //! let a = heap.insert_temp(Object::Num(42.0)); 31 | //! let b = heap.insert_temp(Object::Num(1337.0)); 32 | //! 33 | //! // Turn the numbers into a rooted list 34 | //! let c = heap.insert(Object::List(vec![a, b])); 35 | //! 36 | //! // Change one of the numbers - this is safe, even if the object is self-referential! 37 | //! *heap.get_mut(a).unwrap() = Object::Num(256.0); 38 | //! 39 | //! // Create another number object 40 | //! let d = heap.insert_temp(Object::Num(0.0)); 41 | //! 42 | //! // Clean up unused heap objects 43 | //! heap.clean(); 44 | //! 45 | //! // a, b and c are all kept alive because c is rooted and a and b are its children 46 | //! assert!(heap.contains(a)); 47 | //! assert!(heap.contains(b)); 48 | //! assert!(heap.contains(c)); 49 | //! 50 | //! // Because `d` was temporary and unused, it did not survive the heap clean 51 | //! assert!(!heap.contains(d)); 52 | //! 53 | //! ``` 54 | 55 | pub mod trace; 56 | 57 | use std::{ 58 | cmp::{PartialEq, Eq}, 59 | rc::Rc, 60 | hash::{Hash, Hasher}, 61 | }; 62 | use hashbrown::{HashMap, HashSet}; 63 | use crate::trace::*; 64 | 65 | /// Common items that you'll probably need often. 66 | pub mod prelude { 67 | pub use super::{ 68 | Heap, 69 | Handle, 70 | Rooted, 71 | trace::{Trace, Tracer}, 72 | }; 73 | } 74 | 75 | type Generation = usize; 76 | 77 | /// A heap for storing objects. 78 | /// 79 | /// [`Heap`] is the centre of `broom`'s universe. It's the singleton through with manipulation of 80 | /// objects occurs. It can be used to create, access, mutate and garbage-collect objects. 81 | /// 82 | /// Note that heaps, and the objects associated with them, are *not* compatible: this means that 83 | /// you may not create trace routes (see [`Trace`]) that cross the boundary between different heaps. 84 | pub struct Heap { 85 | last_sweep: usize, 86 | object_sweeps: HashMap, usize>, 87 | obj_counter: Generation, 88 | objects: HashSet>, 89 | rooted: HashMap, Rc<()>>, 90 | } 91 | 92 | impl Default for Heap { 93 | fn default() -> Self { 94 | Self { 95 | last_sweep: 0, 96 | object_sweeps: HashMap::default(), 97 | obj_counter: 0, 98 | objects: HashSet::default(), 99 | rooted: HashMap::default(), 100 | } 101 | } 102 | } 103 | 104 | impl> Heap { 105 | /// Create an empty heap. 106 | pub fn new() -> Self { 107 | Self::default() 108 | } 109 | 110 | fn new_generation(&mut self) -> Generation { 111 | self.obj_counter += 1; 112 | self.obj_counter 113 | } 114 | 115 | /// Adds a new object to this heap that will be cleared upon the next garbage collection, if 116 | /// not attached to the object tree. 117 | pub fn insert_temp(&mut self, object: T) -> Handle { 118 | let ptr = Box::into_raw(Box::new(object)); 119 | 120 | let gen = self.new_generation(); 121 | let handle = Handle { gen, ptr }; 122 | self.objects.insert(handle); 123 | 124 | handle 125 | } 126 | 127 | /// Adds a new object to this heap that will not be cleared by garbage collection until all 128 | /// rooted handles have been dropped. 129 | pub fn insert(&mut self, object: T) -> Rooted { 130 | let handle = self.insert_temp(object); 131 | 132 | let rc = Rc::new(()); 133 | self.rooted.insert(handle, rc.clone()); 134 | 135 | Rooted { 136 | rc, 137 | handle, 138 | } 139 | } 140 | 141 | /// Upgrade a handle (that will be cleared by the garbage collector) into a rooted handle (that 142 | /// will not). 143 | pub fn make_rooted(&mut self, handle: impl AsRef>) -> Rooted { 144 | let handle = handle.as_ref(); 145 | debug_assert!(self.contains(handle)); 146 | 147 | Rooted { 148 | rc: self.rooted 149 | .entry(*handle) 150 | .or_insert_with(|| Rc::new(())) 151 | .clone(), 152 | handle: *handle, 153 | } 154 | } 155 | 156 | /// Count the number of heap-allocated objects in this heap 157 | pub fn len(&self) -> usize { 158 | self.objects.len() 159 | } 160 | 161 | /// Return true if the heap contains the specified handle 162 | pub fn contains(&self, handle: impl AsRef>) -> bool { 163 | let handle = handle.as_ref(); 164 | self.objects.contains(&handle) 165 | } 166 | 167 | /// Get a reference to a heap object if it exists on this heap. 168 | pub fn get(&self, handle: impl AsRef>) -> Option<&T> { 169 | let handle = handle.as_ref(); 170 | if self.contains(handle) { 171 | Some(unsafe { &*handle.ptr }) 172 | } else { 173 | None 174 | } 175 | } 176 | 177 | /// Get a reference to a heap object without checking whether it is still alive or that it 178 | /// belongs to this heap. 179 | /// 180 | /// If either invariant is not upheld, calling this function results in undefined 181 | /// behaviour. 182 | pub unsafe fn get_unchecked(&self, handle: impl AsRef>) -> &T { 183 | let handle = handle.as_ref(); 184 | debug_assert!(self.contains(handle)); 185 | &*handle.ptr 186 | } 187 | 188 | /// Get a mutable reference to a heap object 189 | pub fn get_mut(&mut self, handle: impl AsRef>) -> Option<&mut T> { 190 | let handle = handle.as_ref(); 191 | if self.contains(handle) { 192 | Some(unsafe { &mut *handle.ptr }) 193 | } else { 194 | None 195 | } 196 | } 197 | 198 | /// Get a mutable reference to a heap object without first checking that it is still alive or 199 | /// that it belongs to this heap. 200 | /// 201 | /// If either invariant is not upheld, calling this function results in undefined 202 | /// behaviour. Provided they are upheld, this function provides zero-cost access. 203 | pub unsafe fn get_mut_unchecked(&mut self, handle: impl AsRef>) -> &mut T { 204 | let handle = handle.as_ref(); 205 | debug_assert!(self.contains(handle)); 206 | &mut *handle.ptr 207 | } 208 | 209 | /// Clean orphaned objects from the heap, excluding those that can be reached from the given 210 | /// handle iterator. 211 | /// 212 | /// This function is useful in circumstances in which you wish to keep certain items alive over 213 | /// a garbage collection without the addition cost of a [`Rooted`] handle. An example of this 214 | /// might be stack items in a garbage-collected language 215 | pub fn clean_excluding(&mut self, excluding: impl IntoIterator>) { 216 | let new_sweep = self.last_sweep + 1; 217 | let mut tracer = Tracer { 218 | new_sweep, 219 | object_sweeps: &mut self.object_sweeps, 220 | objects: &self.objects, 221 | }; 222 | 223 | // Mark 224 | self.rooted 225 | .retain(|handle, rc| { 226 | if Rc::strong_count(rc) > 1 { 227 | tracer.mark(*handle); 228 | unsafe { (&*handle.ptr).trace(&mut tracer); } 229 | true 230 | } else { 231 | false 232 | } 233 | }); 234 | let objects = &self.objects; 235 | excluding 236 | .into_iter() 237 | .filter(|handle| objects.contains(&handle)) 238 | .for_each(|handle| { 239 | tracer.mark(handle); 240 | unsafe { (&*handle.ptr).trace(&mut tracer); } 241 | }); 242 | 243 | // Sweep 244 | let object_sweeps = &mut self.object_sweeps; 245 | self.objects 246 | .retain(|handle| { 247 | if object_sweeps 248 | .get(handle) 249 | .map(|sweep| *sweep == new_sweep) 250 | .unwrap_or(false) 251 | { 252 | true 253 | } else { 254 | object_sweeps.remove(handle); 255 | drop(unsafe { Box::from_raw(handle.ptr) }); 256 | false 257 | } 258 | }); 259 | 260 | self.last_sweep = new_sweep; 261 | } 262 | 263 | /// Clean orphaned objects from the heap. 264 | pub fn clean(&mut self) { 265 | self.clean_excluding(std::iter::empty()); 266 | } 267 | } 268 | 269 | impl Drop for Heap { 270 | fn drop(&mut self) { 271 | for handle in &self.objects { 272 | drop(unsafe { Box::from_raw(handle.ptr) }); 273 | } 274 | } 275 | } 276 | 277 | /// A handle to a heap object. 278 | /// 279 | /// [`Handle`] may be cheaply copied as is necessary to serve your needs. It's even legal for it 280 | /// to outlive the object it refers to, provided it is no longer used to access it afterwards. 281 | #[derive(Debug)] 282 | pub struct Handle { 283 | gen: Generation, 284 | ptr: *mut T, 285 | } 286 | 287 | impl Handle { 288 | /// Get a reference to the object this handle refers to without checking any invariants. 289 | /// 290 | /// **You almost certainly do not want to use this function: consider [`Heap::get`] or 291 | /// [`Heap::get_unchecked`] instead; both are safer than this function.** 292 | /// 293 | /// The following invariants must be upheld by you, the responsible programmer: 294 | /// 295 | /// - The object *must* still be alive (i.e: accessible from the heap it was created on) 296 | /// - The object *must not* be mutably accessible elsewhere (i.e: has any live references to 297 | /// it) by any other part of the program. Immutable references are permitted. Other handles 298 | /// (i.e: [`Handle`] or [`Rooted`] are also permitted, provided they are not in use. 299 | /// - That a garbage collection of the heap this object belongs to does not occur while the 300 | /// reference this function creates is live. 301 | /// 302 | /// If *any* of these invariants are not upheld, undefined behaviour will result when using 303 | /// this function. If all are upheld, this function provides zero-cost access to underlying 304 | /// object. 305 | pub unsafe fn get_unchecked(&self) -> &T { 306 | &*self.ptr 307 | } 308 | 309 | /// Get a mutable reference to the object this handle refers to without checking any 310 | /// invariants. 311 | /// 312 | /// **You almost certainly do not want to use this function: consider [`Heap::mutate`] or 313 | /// [`Heap::mutate_unchecked`] instead; both are safer than this function.** 314 | /// 315 | /// The following invariants must be upheld by you, the responsible programmer: 316 | /// 317 | /// - The object *must* still be alive (i.e: accessible from the heap it was created on) 318 | /// - The object *must not* be accessible elsewhere (i.e: has any live references to it), 319 | /// either mutably or immutably, by any other part of the program. Other handles (i.e: 320 | /// [`Handle`] or [`Rooted`] are permitted, provided they are not in use. 321 | /// - That a garbage collection of the heap this object belongs to does not occur while the 322 | /// reference this function creates is live. 323 | /// 324 | /// If *any* of these invariants are not upheld, undefined behaviour will result when using 325 | /// this function. If all are upheld, this function provides zero-cost access to underlying 326 | /// object. 327 | pub unsafe fn get_mut_unchecked(&self) -> &mut T { 328 | &mut *self.ptr 329 | } 330 | } 331 | 332 | impl Copy for Handle {} 333 | impl Clone for Handle { 334 | fn clone(&self) -> Self { 335 | Self { gen: self.gen, ptr: self.ptr } 336 | } 337 | } 338 | 339 | impl PartialEq for Handle { 340 | fn eq(&self, other: &Self) -> bool { 341 | self.gen == other.gen && self.ptr == other.ptr 342 | } 343 | } 344 | impl Eq for Handle {} 345 | 346 | impl Hash for Handle { 347 | fn hash(&self, state: &mut H) { 348 | self.gen.hash(state); 349 | self.ptr.hash(state); 350 | } 351 | } 352 | 353 | impl AsRef> for Handle { 354 | fn as_ref(&self) -> &Handle { 355 | self 356 | } 357 | } 358 | 359 | impl From> for Handle { 360 | fn from(rooted: Rooted) -> Self { 361 | rooted.handle 362 | } 363 | } 364 | 365 | /// A handle to a heap object that guarantees the object will not be cleaned up by the garbage 366 | /// collector. 367 | /// 368 | /// [`Rooted`] may be cheaply copied as is necessary to serve your needs. It's even legal for it 369 | /// to outlive the object it refers to, provided it is no longer used to access it afterwards. 370 | #[derive(Debug)] 371 | pub struct Rooted { 372 | // TODO: Is an Rc the best we can do? It might be better instead to store the strong count with 373 | // the object to avoid an extra allocation. 374 | rc: Rc<()>, 375 | handle: Handle, 376 | } 377 | 378 | impl Clone for Rooted { 379 | fn clone(&self) -> Self { 380 | Self { 381 | rc: self.rc.clone(), 382 | handle: self.handle, 383 | } 384 | } 385 | } 386 | 387 | impl AsRef> for Rooted { 388 | fn as_ref(&self) -> &Handle { 389 | &self.handle 390 | } 391 | } 392 | 393 | impl Rooted { 394 | pub fn into_handle(self) -> Handle { 395 | self.handle 396 | } 397 | 398 | pub fn handle(&self) -> Handle { 399 | self.handle 400 | } 401 | } 402 | 403 | #[cfg(test)] 404 | mod tests { 405 | use super::*; 406 | use std::sync::atomic::{AtomicUsize, Ordering}; 407 | 408 | enum Value<'a> { 409 | Base(&'a AtomicUsize), 410 | Refs(&'a AtomicUsize, Handle>, Handle>), 411 | } 412 | 413 | impl<'a> Trace for Value<'a> { 414 | fn trace(&self, tracer: &mut Tracer) { 415 | match self { 416 | Value::Base(_) => {}, 417 | Value::Refs(_, a, b) => { 418 | a.trace(tracer); 419 | b.trace(tracer); 420 | }, 421 | } 422 | } 423 | } 424 | 425 | impl<'a> Drop for Value<'a> { 426 | fn drop(&mut self) { 427 | match self { 428 | Value::Base(count) | Value::Refs(count, _, _) => 429 | count.fetch_sub(1, Ordering::Relaxed), 430 | }; 431 | } 432 | } 433 | 434 | #[test] 435 | fn basic() { 436 | let count: AtomicUsize = AtomicUsize::new(0); 437 | 438 | let new_count = || { 439 | count.fetch_add(1, Ordering::Relaxed); 440 | &count 441 | }; 442 | 443 | let mut heap = Heap::default(); 444 | 445 | let a = heap.insert(Value::Base(new_count())); 446 | 447 | heap.clean(); 448 | 449 | assert_eq!(heap.contains(&a), true); 450 | 451 | let a = a.into_handle(); 452 | 453 | heap.clean(); 454 | 455 | assert_eq!(heap.contains(&a), false); 456 | 457 | drop(heap); 458 | assert_eq!(count.load(Ordering::Acquire), 0); 459 | } 460 | 461 | #[test] 462 | fn ownership() { 463 | let count: AtomicUsize = AtomicUsize::new(0); 464 | 465 | let new_count = || { 466 | count.fetch_add(1, Ordering::Relaxed); 467 | &count 468 | }; 469 | 470 | let mut heap = Heap::default(); 471 | 472 | let a = heap.insert(Value::Base(new_count())).handle(); 473 | let b = heap.insert(Value::Base(new_count())).handle(); 474 | let c = heap.insert(Value::Base(new_count())).handle(); 475 | let d = heap.insert(Value::Refs(new_count(), a, c)); 476 | let e = heap.insert(Value::Base(new_count())).handle(); 477 | 478 | heap.clean(); 479 | 480 | assert_eq!(heap.contains(&a), true); 481 | assert_eq!(heap.contains(&b), false); 482 | assert_eq!(heap.contains(&c), true); 483 | assert_eq!(heap.contains(&d), true); 484 | assert_eq!(heap.contains(&e), false); 485 | 486 | let a = heap.insert_temp(Value::Base(new_count())); 487 | 488 | heap.clean(); 489 | 490 | assert_eq!(heap.contains(&a), false); 491 | 492 | let a = heap.insert_temp(Value::Base(new_count())); 493 | let a = heap.make_rooted(a); 494 | 495 | heap.clean(); 496 | 497 | assert_eq!(heap.contains(&a), true); 498 | 499 | drop(heap); 500 | assert_eq!(count.load(Ordering::Acquire), 0); 501 | } 502 | 503 | #[test] 504 | fn recursive() { 505 | let count: AtomicUsize = AtomicUsize::new(0); 506 | 507 | let new_count = || { 508 | count.fetch_add(1, Ordering::Relaxed); 509 | &count 510 | }; 511 | 512 | let mut heap = Heap::default(); 513 | 514 | let a = heap.insert(Value::Base(new_count())); 515 | let b = heap.insert(Value::Base(new_count())); 516 | 517 | *heap.get_mut(&a).unwrap() = Value::Refs(new_count(), a.handle(), b.handle()); 518 | 519 | heap.clean(); 520 | 521 | assert_eq!(heap.contains(&a), true); 522 | assert_eq!(heap.contains(&b), true); 523 | 524 | let a = a.into_handle(); 525 | 526 | heap.clean(); 527 | 528 | assert_eq!(heap.contains(&a), false); 529 | assert_eq!(heap.contains(&b), true); 530 | 531 | drop(heap); 532 | assert_eq!(count.load(Ordering::Acquire), 0); 533 | } 534 | 535 | #[test] 536 | fn temporary() { 537 | let count: AtomicUsize = AtomicUsize::new(0); 538 | 539 | let new_count = || { 540 | count.fetch_add(1, Ordering::Relaxed); 541 | &count 542 | }; 543 | 544 | let mut heap = Heap::default(); 545 | 546 | let a = heap.insert_temp(Value::Base(new_count())); 547 | 548 | heap.clean(); 549 | 550 | assert_eq!(heap.contains(&a), false); 551 | 552 | let a = heap.insert_temp(Value::Base(new_count())); 553 | let b = heap.insert(Value::Refs(new_count(), a, a)); 554 | 555 | heap.clean(); 556 | 557 | assert_eq!(heap.contains(&a), true); 558 | assert_eq!(heap.contains(&b), true); 559 | 560 | let a = heap.insert_temp(Value::Base(new_count())); 561 | 562 | heap.clean_excluding(Some(a)); 563 | 564 | assert_eq!(heap.contains(&a), true); 565 | 566 | drop(heap); 567 | assert_eq!(count.load(Ordering::Acquire), 0); 568 | } 569 | } 570 | -------------------------------------------------------------------------------- /src/trace.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A trait used to tell the garbage collector how it may explore an object graph composed of 4 | /// values of type `T`. 5 | /// 6 | /// To implement this, simply call `foo.trace(tracer)` on all traceable children 7 | /// of the type. Note that this trait has default implementations for a variety of common types. 8 | /// 9 | /// # Example 10 | /// ``` 11 | /// use broom::prelude::*; 12 | /// 13 | /// pub enum Object { 14 | /// Num(f64), 15 | /// List(Vec>), 16 | /// } 17 | /// 18 | /// impl Trace for Object { 19 | /// fn trace(&self, tracer: &mut Tracer) { 20 | /// match self { 21 | /// Object::Num(_) => {}, 22 | /// Object::List(objects) => objects.trace(tracer), 23 | /// } 24 | /// } 25 | /// } 26 | /// ``` 27 | pub trait Trace> { 28 | /// Trace *all* child objects of this type. 29 | /// 30 | /// Note that although failing to trace all children is not undefined behaviour on its own, it 31 | /// will mean that objects may be accidentally garbage-collected, and hence that the 32 | /// `_unchecked` methods in this crate will produce undefined behaviour when used to access 33 | /// those objects. 34 | /// 35 | /// In addition, you must ensure that this function does not result in the tracing of objects 36 | /// associated with other heaps: to do so is undefined behaviour. 37 | fn trace(&self, tracer: &mut Tracer); 38 | } 39 | 40 | /// A type used to perform a heap trace. Largely an implementation detail: To implement heap 41 | /// tracing, look at the [`Trace`] trait instead. 42 | pub struct Tracer<'a, T: Trace> { 43 | pub(crate) new_sweep: usize, 44 | pub(crate) object_sweeps: &'a mut HashMap, usize>, 45 | pub(crate) objects: &'a HashSet>, 46 | } 47 | 48 | impl<'a, T: Trace> Tracer<'a, T> { 49 | pub(crate) fn mark(&mut self, handle: Handle) { 50 | let sweep = self.object_sweeps 51 | .entry(handle) 52 | .or_insert(self.new_sweep - 1); 53 | if *sweep != self.new_sweep && self.objects.contains(&handle) { 54 | *sweep = self.new_sweep; 55 | unsafe { (&*handle.ptr).trace(self); } 56 | } 57 | } 58 | } 59 | 60 | impl> Trace for Handle { 61 | fn trace(&self, tracer: &mut Tracer) { 62 | tracer.mark(*self); 63 | } 64 | } 65 | 66 | impl> Trace for Rooted { 67 | fn trace(&self, tracer: &mut Tracer) { 68 | self.handle().trace(tracer); 69 | } 70 | } 71 | 72 | // Impl on standard things 73 | use std::collections::{ 74 | HashMap as StdHashMap, 75 | VecDeque, 76 | LinkedList, 77 | }; 78 | 79 | impl, T: Trace> Trace for [T] { 80 | fn trace(&self, tracer: &mut Tracer) { 81 | self.iter().for_each(|object| object.trace(tracer)); 82 | } 83 | } 84 | 85 | impl, T: Trace> Trace for VecDeque { 86 | fn trace(&self, tracer: &mut Tracer) { 87 | self.iter().for_each(|object| object.trace(tracer)); 88 | } 89 | } 90 | 91 | impl, T: Trace> Trace for LinkedList { 92 | fn trace(&self, tracer: &mut Tracer) { 93 | self.iter().for_each(|object| object.trace(tracer)); 94 | } 95 | } 96 | 97 | impl, K, V: Trace> Trace for StdHashMap { 98 | fn trace(&self, tracer: &mut Tracer) { 99 | self.values().for_each(|object| object.trace(tracer)); 100 | } 101 | } 102 | 103 | impl, T: Trace> Trace for HashSet { 104 | fn trace(&self, tracer: &mut Tracer) { 105 | self.iter().for_each(|object| object.trace(tracer)); 106 | } 107 | } 108 | --------------------------------------------------------------------------------