Observer pattern in Rust

Could you please share the code of implementation that uses Weak references approach?

Well you already posted the implementation I use (sry for skipping my sources :woozy_face:):

As you said it is not the most elegant way but it works.
Indeed my implementation for the channel-solution is based on this post:

//! Implementation of the Observer-Pattern.
//! Original code by 'locka' @ https://stackoverflow.com/questions/37572734/how-can-i-implement-the-observer-pattern-in-rust
//! Modified by farnbams: Substitute Listener by mpsc::Sender

use std::sync::mpsc::Sender;

pub trait Dispatchable<T>
    where T: Clone
{
    fn register_channel(&mut self, tx: Sender<T>);
}

pub struct Dispatcher<T>
    where T: Clone
{
    senders: Vec<(Sender<T>, bool)>,
}

impl<T> Dispatchable<T> for Dispatcher<T>
    where T: Clone
{
    /// Registers a new Sender
    fn register_channel(&mut self, tx: Sender<T>) {
        self.senders.push((tx, true));
    }
}

impl<T> Dispatcher<T>
    where T: Clone
{
    pub fn new() -> Dispatcher<T> {
        Dispatcher { senders: Vec::new() }
    }

    pub fn num_senders(&self) -> usize {
        self.senders.len()
    }

    pub fn dispatch(&mut self, msg: &T) {
        let mut cleanup = false;
        // Send messages
        for (s, living) in self.senders.iter_mut() {
            if let Err(_) = s.send(msg.clone()) {
                cleanup = true;
                *living = false;
                debug!("Receiver dropped => Clean up sender-list");
            }
        }
        // If there were invalid senders, clean up the list
        if cleanup {
            debug!("Dispatcher is cleaning up dead senders");
            self.senders.retain(| (_, ref living) | {
                // Only retain living channels
                *living
            });
        }
    }
}
1 Like

So, I went ahead and tried this, with a reference type I called Sc.

Basically, using this reference type, you can write code like this:

extern crate sc;

use sc::Dropper;
use sc::Sc;

struct Visitable {
    observers: Vec<Sc<Fn(&str) + 'static>>,
}

impl Visitable {
    pub fn new(observer_count: usize) -> Self {
        let mut v = Vec::new();
        v.reserve(observer_count);
        for _i in 0..observer_count {
            v.push(Sc::new());
        }
        Visitable { observers: v }
    }

    pub fn add_log(&self, log: &str) {
        for observer in &self.observers {
            observer.map(|observer| observer(log));
        }
    }

    #[must_use]
    pub fn register_observer<'observer, 'sc>(
        &'sc self,
        observer: &'observer (Fn(&str) + 'static),
    ) -> Option<Dropper<'observer, 'sc, Fn(&str) + 'static>> {
        let sc = self.observers.iter().find(|observer| observer.is_none())?;
        Some(sc.set(observer))
    }
}

fn foo_print(x: &str) {
    println!("Foo received '{}'", x)
}

fn main() {
    let visitable = Visitable::new(10);
    visitable.add_log("Lost for science!");
    {
        let _dropper = visitable.register_observer(&foo_print);
        visitable.add_log("Registered log");
        {
            let name = String::from("Bar");
            let lambda = move |log : &_| println!("{} received '{}'", name, log);
            let _dropper_2 = visitable.register_observer(&lambda);
            visitable.add_log("Registered log 2");
        }
    }
    visitable.add_log("Lost for science!");
}

Storing an object in a Sc does not incur any allocation, clone of copy of the object.
Code is here: GitHub - dureuill/sc: Type for dynamic lifetime erasure in Rust
Caveat: Sc is using unsafe, and I now of at least one cause for unsafety: if you mem::forget a _dropper. See also my other thread.

For the observer pattern, you might be interested in the library I wrote for FlowBetween, flo_binding: it has a few neat features driven by the needs of the larger application. Pertinent to this discussion is the follow() function, which turns a Bound value into a stream of changes, which is another way to solve the lifetime issues talked about here.

That Sc type looks like a really nice idea to me too: sort of the borrowing equivalent of Weak.

This is exactly what I was going for, yes. Should I go all the way and call that WeakRef, WeakBorrow or BorrowWeak?
This only thing that bothers me is that if you mem::forget the Dropper type, you then have a dangling reference in safe code. On the plus side, I believe calling mem::forget is the only way to leak a Dropper, since Dropper is parameterized by the lifetime of the object it points to, so any cycle containing droppers cannot outlive these lifetimes? I wish mem::forget weren't safe for non 'static types^^".

Perhaps what is needed is a way to specify that a type cannot be safely mem::forgotten? How unsafe is my reference type? - #2 by gbutler69