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

Wor*_*der 10 ownership-semantics rust

我有一个实现的草图:

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();
}
Run Code Online (Sandbox Code Playgroud)

我可以Transaction在它们上面使用侦听器,当事务发生时会调用侦听器.既然Listener是特质,我会存储一个Vec<Box<Listener>>.

我有一个很难实现commitTransaction.不知怎的,我必须通过调用commit每个存储的Listeners 来消耗这些盒子,但据我所知,我无法从盒子中移出东西.

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

use*_*342 12

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这样可以Transaction在编写时进行编译,因为在FooListener大小的实现中Self是众所周知的,并且完全可以将对象移出框中并同时使用它们.

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

  • @BobGreen 请参阅[此处](https://doc.rust-lang.org/stable/reference/items/linked-items.html?highlight=self#methods)。总之,您可以使用可选地包装在智能指针中的“Self”,例如“Box”或“Arc”。另请参阅我的[其他答案](/sf/answers/4893167191/),它展示了如何在没有 `Box&lt;Self&gt;` 的情况下实现相同的目标。 (2认同)

use*_*342 5

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

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

trait DynListener {
    fn some_action(&mut self);
    fn commit(&mut self);
}
Run Code Online (Sandbox Code Playgroud)

为了在任何地方都可以使用这个特征Listener,我们将提供该特征的全面实现。通常这样的实现将为DynListener所有类型实现T: Listener。但这在这里不起作用,因为Listener::commit()需要 消耗self,并且DynListener::commit()只接收引用,因此调用Listener::commit()将无法编译,并显示“无法移出借用的内容”。为了解决这个问题,我们实施了DynListenerfor 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

DynListener::commit()从 中取出值Option,调用Listener::commit()该值,并将选项保留为NoneT之所以能够编译,是因为在已知每个个体大小的总体实现中,该值并不是“未确定大小”的。缺点是我们可以对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();
}
Run Code Online (Sandbox Code Playgroud)

操场