I have the following sketch of an implementation:
trait Listener {
fn some_action(&mut self);
fn commit(self);
}
struct FooListener {}
impl Listener for FooListener {
fn some_action(&mut self) {
println!("{:?}", "Action!!");
}
fn commit(self) {
println!("{:?}", "Commit");
}
}
struct Transaction {
listeners: Vec<Box<dyn Listener>>,
}
impl Transaction {
fn commit(self) {
// How would I consume the listeners and call commit() on each of them?
}
}
fn listener() {
let transaction = Transaction {
listeners: vec![Box::new(FooListener {})],
};
transaction.commit();
}
I can have Transaction
s with listeners on them that will call the listener when something happens on that transaction. Since Listener
is a trait, I store a Vec<Box<Listener>>
.
I'm having a hard time implementing commit
for Transaction
. Somehow I have to consume the boxes by calling commit
on each of the stored Listener
s, but I can't move stuff out of a box as far as I know.
How would I consume my listeners on commit?
The accepted answer shows what to do when you have the agency to modify the original Listener
trait. If you don't have that option, i.e. if you control the Transaction
type, but not Listener
and its implementations, read on.
First we create a helper trait that is object-safe because none of its methods consume self
:
trait DynListener {
fn some_action(&mut self);
fn commit(&mut self);
}
To use this trait everywhere Listener
is usable, we will provide a blanket implementation of the trait. Normally such an implementation would implement DynListener
for all types T: Listener
. But that doesn't work here because Listener::commit()
requires consuming self
, and DynListener::commit()
only receives a reference, so calling Listener::commit()
would fail to compile with "cannot move out of borrowed content". To work around that, we implement DynListener
for Option<T>
instead. This allows us to use Option::take()
to get an owned value to pass to Listener::commit()
:
impl<T: Listener> DynListener for Option<T> {
fn some_action(&mut self) {
// self is &mut Option<T>, self.as_mut().unwrap() is &mut T
self.as_mut().unwrap().some_action();
}
fn commit(&mut self) {
// self is &mut Option<T>, self.take().unwrap() is T
self.take().unwrap().commit();
}
}
DynListener::commit()
takes value out of the Option
, calls Listener::commit()
on the value, and leaves the option as None
. This compiles because the value is not "unsized" in the blanket implementation where the size of each individual T
is known. The downside is that we are allowed to call DynListener::commit()
multiple times on the same option, with all attempts but the first panicking at run time.
The remaining work is to modify Transaction
to make use of this:
struct Transaction {
listeners: Vec<Box<dyn DynListener>>,
}
impl Transaction {
fn commit(self) {
for mut listener in self.listeners {
listener.commit();
}
}
}
fn listener() {
let transaction = Transaction {
listeners: vec![Box::new(Some(FooListener {}))],
};
transaction.commit();
}
Playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With