13

我有以下实现草图:

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

我可以在他们身上设置监听器Transaction,当该事务发生某些事情时,它们会调用监听器。由于Listener是一个特征,我存储一个Vec<Box<Listener>>.

我很难commit实施Transaction. 不知何故,我必须通过调用commit每个存储Listener的 s 来消耗这些盒子,但据我所知,我无法将东西移出盒子。

我将如何在提交时消耗我的听众?

4

3 回答 3

17

不允许应用于commit装箱对象,因为 trait 对象不知道它的大小(并且它在编译时不是常量)。由于您计划将侦听器用作盒装对象,您可以做的是确认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. 如果这不可接受,您可以在 trait 中同时声明commit(self)和声明commit_boxed(self: Box<Self>),要求所有类型都实现这两者,可能使用私有函数或宏来避免代码重复。这不是很优雅,但它可以满足盒装和非盒装用例而不会损失性能。

于 2017-10-07T22:37:28.293 回答
3

启用该unsized_locals功能后,自然代码按原样工作

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

// ...

impl Transaction {
    fn commit(self) {
        for l in self.listeners {
            l.commit()
        }
    }
}
于 2019-06-04T16:30:43.007 回答
3

接受的答案显示了当您有机构修改原始Listener特征时该怎么做。如果您没有该选项,即如果您控制Transaction类型,但不控制Listener其实现,请继续阅读。

首先,我们创建一个对象安全的辅助 trait,因为它的方法都没有消耗self

trait DynListener {
    fn some_action(&mut self);
    fn commit(&mut self);
}

为了在任何地方都可以使用这个 trait Listener,我们将提供这个 trait 的全面实现。通常这样的实现会DynListener为所有类型实现T: Listener。但这在这里不起作用,因为Listener::commit()需要 消费self,并且DynListener::commit()只接收引用,因此调用Listener::commit()将无法通过“无法移出借来的内容”进行编译。为了解决这个问题,我们实现DynListener了 for Option<T>。这允许我们使用Option::take()来获取一个拥有的值以传递给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()从 中取出值Option,调用Listener::commit()该值,然后将选项保留为None。这是编译的,因为在每个个体的大小已知的一揽子实现中,该值不是“未确定大小的” T。缺点是我们可以DynListener::commit()在同一个选项上多次调用,所有尝试都在运行时出现恐慌。

剩下的工作是修改Transaction以利用它:

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

操场

于 2021-11-09T17:17:29.437 回答