Mock Callbacks in Drop? Possible?

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());
}
1 Like

Thanks so much! I had figured out everything except the trick of using "Cell" on the count_* locals. This was extremely helpful!

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));
}
2 Likes

That's a neat trick! I'll have to remember that one.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.