“dyn”和泛型有什么区别?

13
我正在阅读一些代码,它有一个 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 {

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


也许这会有所帮助:https://users.rust-lang.org/t/whats-the-difference-between-t-trait-and-dyn-trait/35381/3 - 8176135
1个回答

21

使用 dyn 和类型一起使用会导致动态分派(因此有 dyn 关键字),而使用(受限的)泛型参数会导致单态性。

一般解释

动态分派

动态分派意味着方法调用在运行时解析。通常从运行时资源方面来说比单态性更昂贵。

例如,假设您有以下特征

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

有一个结构体MyStruct实现了某个特质。如果你使用 &dyn MyTrait ,并将MyStruct对象的引用传递给它,那么会发生以下情况:

  • 创建一个"vtable"数据结构。这是一个包含指针fgMyStruct实现的表。
  • 将指向该vtable的指针存储在&dyn MyTrait引用中,因此该引用的大小将是其通常大小的两倍;有时候&dyn引用因此被称为"fat references"。
  • 然后调用fg将导致使用存储在vtable中的指针进行间接函数调用。

单态化

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

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

当你将一个MyStruct传递给它时:

sample(MyStruct);

发生的情况如下:

  • 在编译时,会为MyStruct类型创建一个sample函数的副本。简单来说,就好像你复制并粘贴了sample函数的定义,并将T替换为MyStruct
fn sample__MyStruct(t: MyStruct) { ... }
  • sample(MyStruct)调用被编译成sample__MyStruct(MyStruct)

这意味着通常情况下,单态化在二进制代码大小方面可能更加昂贵(因为您实际上是为不同的类型复制类似的代码块),但与动态分派不同,它没有运行时成本。

单态化在编译时间方面也通常更加昂贵:因为它实际上是代码的复制粘贴,使用单态化的代码库往往需要更长的编译时间。

你的例子

由于FnMut只是一个trait,因此上述讨论直接适用于您的问题。以下是该trait的定义:

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。使用&dyn FnMut<...>将导致动态调度,而使用<T: FnMut<...>>将导致单态性。

我的建议和通用建议

某些情况下需要使用动态分派。例如,如果您有一个实现某个特征的外部对象的Vec,则除了使用动态分派之外别无选择。例如,Vec<Box<dyn Debug>>。但是,如果这些对象在您的代码内部,则可以使用enum类型和单态性。

如果您的特征包含关联类型或通用方法,则必须使用单态性,因为这些特征不是对象安全的。

其他所有内容相等时,我的建议是在您的代码库中选择一个偏好并坚持使用它。从我所见,大多数人倾向于默认使用通用和单态性。


2
好答案!你肯定已经涵盖了这个问题,但我想强调使用 f: &mut dyn FnMut 会导致对 f 的调用动态分派,但是使用泛型会导致对 sample 的调用单态化。因此,在某种意义上,使用泛型可以让您将分派选择“推”到 sample 的调用者中(尤其在这里更加明显,因为 &mut dyn FnMut 本身实现了 FnMut,所以即使是通用版本也仍然可以使用动态分派,由调用者自行决定)。 - trent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接