如何调用在盒装特征对象上消耗自我的方法?
How to call a method that consumes self on a boxed trait object?
我有以下实现草图:
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>>
.
我很难为 Transaction
实施 commit
。不知何故,我必须通过在每个存储的 Listener
上调用 commit
来使用这些盒子,但据我所知,我无法将东西从盒子中移出。
我将如何在提交时使用我的听众?
不允许将 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>)
,要求所有类型都实现这两者,可能使用私有函数或宏来避免代码重复。这不是很优雅,但它可以满足装箱和未装箱的用例而不会损失性能。
与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()
}
}
}
接受的答案显示了当您有权修改原始 Listener
特征时该怎么做。如果您没有该选项,即如果您控制 Transaction
类型,但不控制 Listener
及其实现,请继续阅读。
首先我们创建一个对象安全的辅助特性,因为它的 none 个方法消耗 self
:
trait DynListener {
fn some_action(&mut self);
fn commit(&mut self);
}
为了在任何地方使用这个特性 Listener
是可用的,我们将提供一个 全面 特性的实现。通常这样的实现会为所有类型 T: Listener
实现 DynListener
。但这在这里不起作用,因为 Listener::commit()
需要消耗 self
,而 DynListener::commit()
仅接收引用,因此调用 Listener::commit()
将无法编译并显示“无法移出借用内容”。为了解决这个问题,我们为 Option<T>
实现了 DynListener
。这允许我们使用 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();
}
我有以下实现草图:
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>>
.
我很难为 Transaction
实施 commit
。不知何故,我必须通过在每个存储的 Listener
上调用 commit
来使用这些盒子,但据我所知,我无法将东西从盒子中移出。
我将如何在提交时使用我的听众?
不允许将 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>)
,要求所有类型都实现这两者,可能使用私有函数或宏来避免代码重复。这不是很优雅,但它可以满足装箱和未装箱的用例而不会损失性能。
与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()
}
}
}
接受的答案显示了当您有权修改原始 Listener
特征时该怎么做。如果您没有该选项,即如果您控制 Transaction
类型,但不控制 Listener
及其实现,请继续阅读。
首先我们创建一个对象安全的辅助特性,因为它的 none 个方法消耗 self
:
trait DynListener {
fn some_action(&mut self);
fn commit(&mut self);
}
为了在任何地方使用这个特性 Listener
是可用的,我们将提供一个 全面 特性的实现。通常这样的实现会为所有类型 T: Listener
实现 DynListener
。但这在这里不起作用,因为 Listener::commit()
需要消耗 self
,而 DynListener::commit()
仅接收引用,因此调用 Listener::commit()
将无法编译并显示“无法移出借用内容”。为了解决这个问题,我们为 Option<T>
实现了 DynListener
。这允许我们使用 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();
}