How to call a method that consumes self on a boxed trait object?

主宰稳场 提交于 2019-12-17 19:40:46

问题


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 Transactions 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 Listeners, but I can't move stuff out of a box as far as I know.

How would I consume my listeners on commit?


回答1:


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 that commit will be invoked on the box and change its signature accordingly:

trait Listener {
    fn some_action(&mut self);
    fn commit(self: Box<Self>);
}

struct FooListener {}

impl Listener for FooListener {
    fn some_action(&mut self) {
        println!("{:?}", "Action!!");
    }

    fn commit(self: Box<Self>) {
        println!("{:?}", "Commit");
    }
}

This enables Transaction to compile as you wrote it, because inside the implementation of FooListener the size of Self 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 a Box. If that is not acceptable, you could declare both commit(self) and commit_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.




回答2:


With the unsized_locals feature enabled, the natural code works as-is:

// 1.37.0-nightly 2019-06-03 6ffb8f53ee1cb0903f9d
#![feature(unsized_locals)]

// ...

impl Transaction {
    fn commit(self) {
        for l in self.listeners {
            l.commit()
        }
    }
}


来源:https://stackoverflow.com/questions/46620790/how-to-call-a-method-that-consumes-self-on-a-boxed-trait-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!