Search code examples
rustclosures

Closure and value lifetimes


I'm only learning Rust and I'm stumbling upon some stuff, as all, I suppose. And this one I cannot find an answer on my own and I've tried.

What I'm doing is playing around message stream abstraction and it's going fine unless I'd know how to write this test:

        #[test]
        fn take_1() {
            let mut delivery_count = 1;
            let delivery_listener = |item: &i32| {
                delivery_count += 1;
            };
            let stream = super::Stream::<i32>::new(Some(Box::new(delivery_listener)));
            // [Push 10 messages to the stream here]
            assert_eq!(delivery_count, 10);
        }

Here I'm implementing a kind of ack mechanism - I push messages to Stream object and list for delivery confirm through closure and that obviously isn't really working and fails with following err:

`delivery_count` does not live long enough
borrowed value does not live long enough

I did try same approach with Vec<i32> and even with Rc<RefCell<i32>> and miserably failed each time as there was no way I'd manage to attach a lifetime to that variable. I'm quite sure this is something simple I'm missing - I need community help! ;)

EDIT: Here's a reproducible example:

struct QuasiStream<T> {
    delivery_listener: Option<Rc<RefCell<Box<dyn FnMut(&T)>>>>,
}
impl <T> QuasiStream<T> {
    pub fn new(delivery_listener: Option<Box<dyn FnMut(&T)>>) -> Self {
        QuasiStream {
            delivery_listener: match delivery_listener {
                Some(delivery_listener) => Some(Rc::new(RefCell::new(delivery_listener))),
                None => None,
            },
        }
    }
    pub fn publish(&self, item: T) {
        if let Some(delivery_listener) = &self.delivery_listener {
            delivery_listener.as_ref().borrow_mut()(&item);
        }
    }
}

#[test]
fn take_1() {
    let mut delivery_count = 0;
    let delivery_listener = |item: &i32| {
        delivery_count += 1;
    };

    let stream = QuasiStream::<i32>::new(Some(Box::new(delivery_listener)));
    for i in 0..10 {
        stream.publish(i);
    }
    // [Push messages to the stream here]
    assert_eq!(delivery_count, 10);
}

Solution

  • dyn Trait is interpreted as dyn Trait + 'static, which is why the compiler wants that the borrowed reference lives forever.

    Add explicit lifetime parameters to fix that.

    Then, you still get a borrow checker complaint because delivery_count is borrowed mutably for stream and then you want to print it. So for demonstration purposes, we put the stream stuff into its own block so the stream gets dropped and the borrow gets released.

    See here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1c7df989bff3d46ba8113255f0f1fe7b

    use std::cell::RefCell;
    use std::rc::Rc;
    struct QuasiStream<'a, T> {
        delivery_listener: Option<Rc<RefCell<Box<dyn FnMut(&T) + 'a>>>>,
    }
    impl<'a, T> QuasiStream<'a, T> {
        pub fn new(delivery_listener: Option<Box<dyn FnMut(&T) + 'a>>) -> Self {
            QuasiStream {
                delivery_listener: match delivery_listener {
                    Some(delivery_listener) => Some(Rc::new(RefCell::new(delivery_listener))),
                    None => None,
                },
            }
        }
        pub fn publish(&self, item: T) {
            if let Some(delivery_listener) = &self.delivery_listener {
                delivery_listener.as_ref().borrow_mut()(&item);
            }
        }
    }
    
    #[test]
    fn take_1() {
        let mut delivery_count = 0;
        let delivery_listener = |item: &i32| {
            delivery_count += 1;
        };
    
        {
            let stream = QuasiStream::<i32>::new(Some(Box::new(delivery_listener)));
            for i in 0..10 {
                stream.publish(i);
            }
        }
    
        // [Push messages to the stream here]
        assert_eq!(delivery_count, 10);
    }