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<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?
Applying
commit
to the boxed object is not allowed because the trait object doesn't know its size (and it's not constant at compile-time). Since you plan to use listeners as boxed objects, what you can do is acknowledge thatcommit
will be invoked on the box and change its signature accordingly:This enables
Transaction
to compile as you wrote it, because inside the implementation ofFooListener
the size ofSelf
is well known and it is perfectly possible to move the object out of the box and consume both.The price of this solution is that
Listener::commit
now requires aBox
. If that is not acceptable, you could declare bothcommit(self)
andcommit_boxed(self: Box<Self>)
in the trait, requiring all types to implement both, possibly using private functions or macros to avoid code duplication. This is not very elegant, but it would satisfy both the boxed and unboxed use case without loss of performance.