`dyn`和泛型有什么区别?

我正在阅读一些代码,它有一个consume函数可以让我传递我自己的函数f

fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
    where
        F: FnOnce(&mut [u8]) -> Result<R>,

我写了一些类似的代码,但像这样:

pub fn phy_receive(
        &mut self,
        f: &mut dyn FnMut(&[u8])
    ) -> u8 {

公平地说,除了FnOncevs之外,我不知道有什么区别FnMut。使用dyn与泛型类型参数来指定此函数有什么区别?

回答

使用dynwith 类型会导致动态调度(因此是dyn关键字),而使用(受约束的)泛型参数会导致单态。

一般说明

动态调度

动态分派意味着在运行时解析方法调用。就运行时资源而言,它通常比单态更昂贵。

例如,假设您有以下特征

trait MyTrait {
  fn f(&self);
  fn g(&self);
}

和一个MyStruct实现该特性的结构。如果您使用dyn对该特征的引用(例如&dyn MyTrait),并将对MyStruct对象的引用传递给它,则会发生以下情况:

  • 创建了一个“vtable”数据结构。这是包含指向该表MyStruct的实现方式fg
  • 指向此 vtable 的指针与&dyn MyTrait引用一起存储,因此引用将是其通常大小的两倍;&dyn由于这个原因,有时引用被称为“胖引用”。
  • 调用fg然后将导致使用存储在 vtable 中的指针进行间接函数调用。

单态

单态意味着代码是在编译时生成的。它类似于复制和粘贴。在上一节中使用MyTraitMyStruct定义,假设您有一个如下所示的函数:

fn sample<T: MyTrait>(t: T) { ... }

然后你传递MyStruct给它:

sample(MyStruct);

发生的情况如下:

  • 在编译期间,sample专门为该MyStruct类型创建函数的副本。用非常简单的术语来说,这就像您复制并粘贴了sample函数定义并替换TMyStruct
fn sample__MyStruct(t: MyStruct) { ... }
  • sample(MyStruct)呼叫被编译成sample__MyStruct(MyStruct)

这意味着一般来说,单态在二进制代码大小方面可能更昂贵(因为您本质上是在复制类似的代码块,但用于不同的类型),但没有像动态调度那样的运行时成本。

你的榜样

由于FnMut这只是一个特征,因此上述讨论直接适用于您的问题。这是特征定义:

pub trait FnMut<Args>: FnOnce<Args> {
    pub extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

撇开extern "rust-call"怪异不谈,这就是MyTrait上面的特征。这个特性是由某些 Rust 函数实现的,所以这些函数中的任何一个都类似于MyStruct上面的。using&dyn FnMut<...>将导致动态调度,而 using<T: FnMut<...>>将导致单态。

我的 2 美分和一般建议

某些情况需要您使用动态调度。例如,如果你有一个Vec实现某个特征的外部对象,你别无选择,只能使用动态分派。例如,
Vec<Box<dyn Debug>>

但是,如果这些对象在您的代码内部,您可以使用enum类型和单态。

除此之外,在所有条件相同的情况下,我的建议是,如果可以,请使用单态,如果必须,请使用动态调度。就我个人而言,我注意到在我的大部分代码中,我最终只调用了一次或两次函数。如果你只调用一次,你就可以免费获得单态性,因为二进制代码只会生成一次。另一方面,使用dyn引用可能会导致更简单的代码——你的里程可能会有所不同。


以上是`dyn`和泛型有什么区别?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>