如何调用在盒装特征对象上消耗self的方法?

时间:2017-10-07 13:35:54

标签: rust ownership-semantics

我有一个实现的草图:

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

我可以拥有Transaction个侦听器,当该事务发生时会调用侦听器。由于Listener是一个特征,我存储Vec<Box<Listener>>

我很难为commit实施Transaction。不知何故,我必须通过在每个存储的commit上调用Listener来使用这些框,但据我所知,我无法将这些内容移出框中。

我如何在提交时使用我的侦听器?

2 个答案:

答案 0 :(得分:8)

不允许将commit应用于盒装对象,因为特征对象不知道它的大小(并且它在编译时不是常量)。由于您计划将侦听器用作盒装对象,因此您可以确认将在框中调用commit并相应地更改其签名:

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

这使得Transaction能够在编写时进行编译,因为在FooListener的实现中,Self的大小是众所周知的,并且完全有可能将对象移出盒子并消耗两者。

此解决方案的价格是Listener::commit现在需要一个Box。如果这是不可接受的,您可以在特征中声明commit(self)commit_boxed(self: Box<Self>),要求所有类型都实现这两种类型,可能使用私有函数或宏来避免代码重复。这不是很优雅,但它可以同时满足盒装和无盒装用例而不会降低性能。

答案 1 :(得分:1)

启用unsized_locals功能后,自然代码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()
        }
    }
}