Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between `dyn` and generics?

Tags:

rust

I'm reading some code and it has a consume function which makes it possible for me to pass my own function f.

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

I wrote some similar code, but like this:

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

and to be fair I don't know what is the difference, aside from FnOnce vs FnMut. What is the difference between using dyn vs a generic type parameter to specify this function?

like image 526
Gatonito Avatar asked Mar 11 '21 03:03

Gatonito


People also ask

How do generics compare to brand drugs?

Generics have to prove they are bioequivalent to the brand version. Bioequivalence means the generic works the same way and provides the same benefits. It’s the FDA’s job to monitor drug safety.

What is an original drug and generic drug?

Original drug, as the name suggests, is a new drug that has never been created before. It has a patent period of 20 years and hence is also called a patent drug. However, when the patent expires, other drug manufacturers can start produce this new drug, and the drug they produced is called generic drug.

What is the difference between generic and non-generic collection?

A Non-generic collection is a specialized class for data storage and retrieval that provides support for stacks, queues, lists and hashtables. The key difference between Generic and Non-generic Collection in C# is that a Generic Collection is strongly typed while a Non-Generic Collection is not strongly typed. 1. Overview and Key Difference 2.

What is a generic Dictionary in C?

The Generic Dictionary in C# is a collection of keys and values. When there is a statement as follows, the object dictionary1 can store int type keys and string type values. A Generic SortedList collection stores key and value pairs in ascending order of key by default.


1 Answers

Using dyn with types results in dynamic dispatch (hence the dyn keyword), whereas using a (constrained) generic parameter results in monomorphism.

General explanation

Dynamic dispatch

Dynamic dispatch means that a method call is resolved at runtime. It is generally more expensive in terms of runtime resources than monomorphism.

For example, say you have the following trait

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

and a struct MyStruct which implements that trait. If you use a dyn reference to that trait (e.g. &dyn MyTrait), and pass a reference to a MyStruct object to it, what happens is the following:

  • A "vtable" data structure is created. This is a table containing pointers to the MyStruct implementations of f and g.
  • A pointer to this vtable is stored with the &dyn MyTrait reference, hence the reference will be twice its usual size; sometimes &dyn references are called "fat references" for this reason.
  • Calling f and g will then result in indirect function calls using the pointers stored in the vtable.

Monomorphism

Monomorphism means that the code is generated at compile-time. It's similar to copy and paste. Using MyTrait and MyStruct defined in the previous section, imagine you have a function like the following:

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

And you pass a MyStruct to it:

sample(MyStruct);

What happens is the following:

  • During compile time, a copy of the sample function is created specifically for the MyStruct type. In very simple terms, this is as if you copied and pasted the sample function definition and replaced T with MyStruct:
fn sample__MyStruct(t: MyStruct) { ... }
  • The sample(MyStruct) call gets compiled into sample__MyStruct(MyStruct).

This means that in general, monomorphism can be more expensive in terms of binary code size (since you are essentially duplicating similar chunks of code, but for different types), but there's no runtime cost like there is with dynamic dispatch.

Monomorphism is also generally more expensive in terms of compile times: because it essentially does copy-paste of code, codebases that use monomorphism abundantly tend to compile a bit slower.

Your example

Since FnMut is just a trait, the above discussion applies directly to your question. Here's the trait definition:

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

Disregarding the extern "rust-call" weirdness, this is a trait just like MyTrait above. This trait is implemented by certain Rust functions, so any of those functions is analogous to MyStruct from above. Using &dyn FnMut<...> will result in dynamic dispatch, and using <T: FnMut<...>> will result in monomorphism.

My 2 cents and general advice

Certain situations will require you to use a dynamic dispatch. For example, if you have a Vec of external objects implementing a certain trait, you have no choice but to use dynamic dispatch. For example, Vec<Box<dyn Debug>>. If those objects are internal to your code, though, you could use an enum type and monomorphism.

If your trait contains an associated type or a generic method, you will have to use monomorphism, because such traits are not object safe.

Everything else being equal, my advice is to pick one preference and stick with it in your codebase. From what I've seen, most people tend to prefer defaulting to generics and monomorphism.

like image 124
Enn Michael Avatar answered Oct 22 '22 13:10

Enn Michael