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 ):
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
});
}
}
}
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