【问题标题】:How to call a method that consumes self on a boxed trait object?如何调用在盒装特征对象上使用 self 的方法?
【发布时间】:2018-03-19 03:33:57
【问题描述】:

我有以下实现的草图:

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

我可以让Transactions 带有监听器,当该事务发生某些事情时,它们会调用监听器。由于Listener 是一个特征,我存储了一个Vec&lt;Box&lt;Listener&gt;&gt;

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

我将如何在提交时使用我的听众?

【问题讨论】:

  • 从盒子中取出“东西”很容易; you just dereference it。您的情况更复杂,因为您不再知道盒子内存储的值有多大。这意味着您会收到一个错误:无法移动侦听器类型的值:无法静态确定侦听器的大小

标签: rust ownership-semantics


【解决方案1】:

不允许将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。如果这不可接受,您可以在 trait 中同时声明 commit(self)commit_boxed(self: Box&lt;Self&gt;),要求所有类型都实现这两者,可能使用私有函数或宏来避免代码重复。这不是很优雅,但它可以同时满足盒装和非盒装用例而不会损失性能。

【讨论】:

  • 这对特征对象有用吗?比如,如果我有let box: Box&lt;Listener&gt;,我可以打电话给box.commit();吗?
  • 我问是因为Self 将成为 FooListener,而不是 Listener,对吗?
  • @Lucretiel 是的,答案(也可能是问题)是在考虑特征对象的情况下编写的。
  • struct方法的第一个参数self可以分配哪些类型?有没有关于这种用法的进一步说明? (官方教程里找不到。)
  • @BobGreen 参见例如here。总之,您可以使用Self 可选地包裹在一个智能指针中,如BoxArc。另请参阅我的other answer,它展示了如何在没有Box&lt;Self&gt; 的情况下实现相同的目标。
【解决方案2】:

启用unsized_locals 功能后,自然码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()
        }
    }
}

【讨论】:

    【解决方案3】:

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

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

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

    要在Listener 可用的任何地方使用此特征,我们将提供该特征的blanket 实现。通常这样的实现会为所有类型T: Listener 实现DynListener。但这在这里不起作用,因为Listener::commit() 需要消耗self,而DynListener::commit() 只接收一个引用,因此调用Listener::commit() 将无法编译,并出现“无法移出借用内容”。为了解决这个问题,我们为Option&lt;T&gt; 实现了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();
    }
    

    Playground

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-01
      • 2016-10-05
      • 1970-01-01
      • 2022-12-02
      • 1970-01-01
      • 2018-08-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多