I'm trying to create a mock that will invoke a callback in its drop implementation so that I can test out my Vector implementation to ensure it is correctly dropping items that need to be dropped. Here is the unit test I am attempting to write:
#[test]
fn it_drops_remaining_items_when_it_is_dropped() {
struct MockDroppable {
drop_callback: Box<dyn FnOnce()>,
}
impl MockDroppable {
fn new<F>(drop_callback: F) -> Self
where
F: FnOnce() + 'static,
{
Self {
drop_callback: Box::new(drop_callback),
}
}
}
impl Drop for MockDroppable {
fn drop(&mut self) {
(self.drop_callback)();
}
}
let mut count_1 = 0;
let mut count_2 = 0;
let mut count_3 = 0;
let mut v = Vector::<MockDroppable>::new();
v.push(MockDroppable::new(|| count_1 += 1));
v.push(MockDroppable::new(|| count_2 += 1));
v.push(MockDroppable::new(|| count_3 += 1));
let save3 = v.pop();
drop(v);
assert_eq!(1, count_1);
assert_eq!(1, count_2);
assert!(save3.is_some());
assert_eq!(0, count_3);
}
How do I make the lifetimes correct and make the callbacks invokable from the drop? Is this possible? Right now, this won't compile.
You can use Cell to fix the borrow-checker issues, Option::take() to fix the drop() method, and an additional lifetime parameter on MockDroppable to allow borrowing at all (Rust Playground):
#[test]
fn it_drops_remaining_items_when_it_is_dropped() {
use std::cell::Cell;
struct MockDroppable<'a> {
drop_callback: Option<Box<dyn FnOnce() + 'a>>,
}
impl<'a> MockDroppable<'a> {
fn new<F>(drop_callback: F) -> Self
where
F: FnOnce() + 'a,
{
Self {
drop_callback: Some(Box::new(drop_callback)),
}
}
}
impl Drop for MockDroppable<'_> {
fn drop(&mut self) {
self.drop_callback.take().unwrap()();
}
}
let count_1 = Cell::new(0);
let count_2 = Cell::new(0);
let count_3 = Cell::new(0);
let mut v = Vec::<MockDroppable>::new();
v.push(MockDroppable::new(|| count_1.set(count_1.get() + 1)));
v.push(MockDroppable::new(|| count_2.set(count_2.get() + 1)));
v.push(MockDroppable::new(|| count_3.set(count_3.get() + 1)));
let save3 = v.pop();
drop(v);
assert_eq!(1, count_1.get());
assert_eq!(1, count_2.get());
assert!(save3.is_some());
assert_eq!(0, count_3.get());
}
Btw, another trick is to use Rc<()>, and take advantage of the ref-counting methods; it's effectively quite akin to using Cell<usize>, but without having to define helper stuff:
#[test]
fn it_drops_remaining_items_when_it_is_dropped ()
{
let ref count_1 = Rc::new(());
let ref count_2 = Rc::new(());
let ref count_3 = Rc::new(());
let mut v = Vector::<Rc<()>>::new();
v.push(count_1.clone());
v.push(count_2.clone());
v.push(count_3.clone());
let save3 = v.pop();
drop(v);
assert_eq!(2 - 1, Rc::strong_count(count_1));
assert_eq!(2 - 1, Rc::strong_count(count_2));
assert!(save3.is_some());
assert_eq!(2 - 0, Rc::strong_count(count_3));
}