[英]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
s,它們會在該事務發生某些事情時調用偵聽器。 由於Listener
是一個 trait,我存儲了一個Vec<Box<Listener>>
。
我很難為Transaction
實現commit
。 不知何故,我必須通過在每個存儲的Listener
上調用commit
來消耗這些盒子,但據我所知,我無法將東西移出盒子。
我將如何在提交時消耗我的聽眾?
不允許將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
。 如果這是不可接受的,您可以在特征中同時聲明commit(self)
和commit_boxed(self: Box<Self>)
,要求所有類型都實現這兩者,可能使用私有函數或宏來避免代碼重復。 這不是很優雅,但它可以在不損失性能的情況下滿足裝箱和未裝箱的用例。
啟用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()
}
}
}
接受的答案顯示了當您讓機構修改Listener
特征時如何繼續。 如果您沒有該選項,即如果您控制Transaction
類型,但不控制Listener
及其實現,請繼續閱讀。
您可以創建另一個可以在 trait 對象中使用的 trait,因為它不需要消耗self
:
trait XListener {
fn some_action(&mut self);
fn commit(&mut self);
}
為了在Listener
可用的任何地方使用此 trait,我們提供了該 trait 的全面實現。 通常這樣的實現會為所有類型T: Listener
實現XListener
。 但這在這里不起作用,因為Listener::commit()
需要消耗self
,而XListener::commit()
只接收一個引用,所以它不能調用Listener::commit()
。 為了解決這個問題,我們為Option<T>
實現了XListener
。 這允許我們使用Option::take()
來獲取一個擁有的值並傳遞給 `Listener::commit():
impl<T: Listener> XListener for Option<T> {
fn some_action(&mut self) {
self.as_mut().unwrap().some_action();
}
fn commit(&mut self) {
self.take().unwrap().commit();
}
}
XListener::commit()
從Option
取出值,對該值調用Listener::commit()
,並將該選項XListener::commit()
為None
。 這是編譯的,因為在每個單獨T
的大小已知的全面實現中,該值不是“未確定大小”的。 缺點是我們可以在同一個選項上多次調用XListener::commit()
,除了運行時第一次恐慌之外的所有嘗試。
剩下的工作是修改Transaction
以利用這個:
struct Transaction {
listeners: Vec<Box<dyn XListener>>,
}
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();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.