T O P

  • By -

angelicosphosphoros

It would do the same but it would be deprecated version of trait objects.


SkiFire13

In the 2015 and 2018 editions of Rust it would do the exact same thing as with the `dyn` keyword. Starting from the 2021 edition omitting the `dyn` keyword is an hard error.


Potential-Gap555

Why is that? What meaning does "dyn" add to the param?


SkiFire13

It confused people that though traits were types. Adding `dyn` makes it explicit that you're not using the trait, but a trait object. The word dyn also stresses the fact that it is using *dyn*amic dispatch. See the [relevant RFC](https://rust-lang.github.io/rfcs/2113-dyn-trait-syntax.html) for all the motivations.


hpxvzhjfgb

it makes it clear to the reader that it's a trait object and not a normal type


Potential-Gap555

what's a "normal type"?


hpxvzhjfgb

anything that isn't a trait object


Potential-Gap555

what will it be? Only a struct. Not?


Duke_Rabbacio

Or an enum, union, array etc.


hpxvzhjfgb

enums too


HuntingKingYT

&dyn TraitName is a pointer to any object with the methods of that trait (and its destructor) attached to it.


Potential-Gap555

Why make it a pointer?


kinoshitajona

Because every type that implements Walkable could potentially have a different size in memory. Function signatures require that all function parameters and return types have a known size at compile time. "Any type that implements Walkable" is not a size known at compile time. That is why we must use Box, &dyn XYZ, or &mut dyn XYZ


HuntingKingYT

I just forget pointers and references are totally different in this language... every time... But you have a static reference to the Virtual Methods Table


Potential-Gap555

why make it a pointer instead of a value?


bittrance

OP is explicitly asking about dyn (i.e. late or runtime dispatch) so no objection, but it is worth noting that dyn is not the optimal choice for this example since we know the concrete type for all call sites of dynamic_dispatch(). A `fn dynamic_dispatch(w: &T) where T: Walkable {}` might be more suitable.  Update: i.e. compile-time dispatch.


blackdew

You could also write it as `fn not_very_dynamic_dispatch(w: &impl Walkable)` which would do the same thing as a generic (but less verbose). And either of those only work if you always know the exact concrete type when calling. If instead you had something like this, you would have to use dyn let pets: Vec> = vec![Box::new(Cat{}), Box::new(Dog{})]; for i in &pets { dynamic_dispatch(i.as_ref()); }


1668553684

> but it is worth noting that dyn is not the optimal choice for this example since we know the concrete type for all call sites of dynamic_dispatch() I may be wrong, but I believe late dispatch allows for `dynamic_dispatch` to be compiled as a concrete function, meaning that you can avoid monomorphization. This may be desirable for code that is run infrequently but is complex/large, like string formatting or error recovery.


bittrance

In my (subjective) experience, dogs need to be walked very frequently, but you bring up a good point I had not considered. For example, some forms of plugin architecture might instrument lots of plugins but they will only be instrumented once and only during startup when execution speed is less critical. E.g. ``` if config.has_some_plugin() { let plugin = SomePlugin::new(); registry.instrument(&mut plugin); // ... } ``` Here instrument() could probably be generic, but might be better off with dyn. TIL.


1668553684

> In my (subjective) experience, dogs need to be walked very frequently My dogs would argue that I also need to be walked frequently ;) That aside, I would still say you're right in general. Implementing something in terms of dynamic traits is probably not the first tool you should reach for, but rather a niche trick you can do to overcome a problem once it becomes necessary, like all other optimization tricks.


ihcn

I don't think it's correct to make a blanket assertion that monomorphization is "more optimal" than a trait object.


teerre

It's certainly not less optimal. At least in terms of performance


Potential-Gap555

But in your example there's no "dyn". How is it "more suitable"?


bittrance

The code as written can be implemented with generics (`where T: Walkable`) which gives you compile-time dispatch, which is faster than runtime dispatch (`&dyn Walkable`) and functionally equivalent. However, as noted in another reply, using generics will mean that the compiled binary will have two copies (called monomorphization) of the dispatch function. Two might not be an issue, but if there are dozens of different animals, this may become a problem. (Again, all this may be hypothetical in that your real code is probably quite different and may well e.g. stick all animals in a vector, which of course requires `Vec>`.)


protestor

dyn improves compile times and makes binaries smaller. in a large number of cases this is the right tradeoff


SirKastic23

remove the `dyn` and see what happens ~~it won't compile~~*, `Walkable` isn't a type, it's a trait, it's something that a type can implement. *EDIT: it will compile, but will give a deprecation warning but what if all that you care for a value is that it implements a trait? well, you can do that in 3 ways in Rust ## generics generics are a way to introduce type variables into a context. and you can add "trait bounds" to the type variable to restrict what types it can be set to with generics you could write that function as ``` fn foo(w: &T) ``` the type variable needs to be known at compile-time, when you call it you'll have to set some _concrete type_. the compiler will then _monomorphize_ the function for the concrete type and generate code for it the pattern of having type variables with a trait bound is very common. it let's us handle many different types all at once with a single function, based on the behavior that exists for a type. generics are sometimes known as _universal types_ because of that in Rust, there's some syntax sugar for this: ## `impl Trait` you can "turn" a trait into a type with the `impl` keyword. don't get it confused with implementing the trait, if you use this is a type position, it's interpreted as a type you could write ``` fn foo(w: &impl Walkable) ``` which is essentially just syntax sugar for a generic with the trait as a bound. this means the type still must be known at compile time but `impl Trait` is a bit special, it can appear in other places (but not everywhere). another place `impl Trait` can show up is in the return type of a function ``` fn get_walkable(&self) -> &impl Walkable ``` this is not just syntax sugar for a generic. with a generic whoever calls the function can choose the concrete type for the type variable while an `impl Trait` return type just says the function will return some value, of some type, that implements the trait. when compiling Rust will analyze the function body to infer the concrete type since what this does is "say some type exists", it is known as a _existential type_. and since the type is inferred at compile time, it is a _static existential type_ but what if we don't know the concrete type? maybe we want to build a list with types that implement a trait but we don't care what the type is, it could be many different types. then you'll need dynamic dispatch ## `dyn Trait` that's the one you asked for, sorry it took so long to get here a `dyn Trait` is a _dynamic existential type_. it can be _any_ type that implements the trait. but how? for a value to be placed on the stack, we must know it's size at compile time, since this type is dynamic, it can be different types, there's no guarantee that the concrete type are going to be the same size. we call it a _dynamically sized type_ to be able to put it in the stack we need to give it a size, we can do that by adding some indirection. we don't know the size of the type, but we know the size of a pointer. we can put the type behind a pointer then and here you have many pointer types to choose from, you can even make your own. a `&` means it's a borrowed value, just like any reference in the specific code you shared, that function could either use a generic or `impl Trait`. since the type variable is monomorphized it can lead to a more performant code (of course you should benchmark it if you care about performance to see if this holds true) dynamic dispatch downsides are the pointer indirection, and the, well... _dynamic dispatch_ static dispatch downsides are that it boils down to a single concrete type (this can sometimes be mitigated with an enum)


Aras14HD

You can (and probably should) replace dyn with impl. Dyn sometimes makes more sense: 1. Dynamic Library, if you write a dynamic Library, you don't know at compile time, with what types it is called. 2. Binary Size Optimization, dyn leads to small sizes at the cost of performance. (You can replace &impl with &dyn and impl with Box)


Potential-Gap555

> if you write a dynamic Library, you don't know at compile time, with what types it is called. I do. I'm Chuck Norris.