簡體   English   中英

如何在裝箱的 trait 對象上調用消耗 self 的方法?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM