top | item 47163802

(no title)

menaerus | 3 days ago

Ah, I see. Do I understand correctly that this means that for a given instance of polymorphic object I can switch between static polymorphism and dynamic dispatch, and use them both simultaneously? How is this useful in practical terms, like why would I want to do it?

discuss

order

dminik|3 days ago

Sort of. Given an instance (can even be a primitive) you can obtain a dyn reference to a trait it implements simply by casting it.

let a: i32 = 12

let b = &a as &dyn std::string::ToString; // i32 implements the ToString trait

let c = a.to_string(); // Static dispatch

let d = b.to_string(); // Dynamic dispatch through dyn reference

Note that there's not really any polymorphic objects in rust. All polymorphism in this case goes through the dyn reference which contains a pointer to a vtable for a specific trait.

Additionally, going from a dyn reference to a type-specific reference is not easy. Also, certain methods and traits are not dyn-compatible, mostly due to generic parameters.

The main use comes in with various libraries. Doing dynamic dispatch on a specific type is not very useful, but your library might expose a trait which you then call some methods on. If you accept a generic parameter (eg. impl Trait) each such invocation will cause monomorphization (the function body is compiled separately for each generic type combination). This can obviously bloat compile times.

Using a dyn reference in your API will result in only a single version being compiled. The downside is the inability to inline or optimize based on the type.

One additional use I found is that you can sometimes get around the divergent expression type in match expressions. Say you need to print out some values of different types:

let value: &dyn Display = match foo { A(numeric_id) => &numeric_id, B(string_name) => &string_name, C => &"static str", };

This would not work without dyn as each value has a different type.

menaerus|2 days ago

Ah, I see. Thanks for the example. I think I understand now. In C++ problem of monomorphization, or potential bloat due to excessive template instantiations, is normally handled at the linker level but can be controlled at the code level too through either by rewriting the code with some other type-erasure technique or simply by extracting bits of not-quite-generic-code into smaller non-generic entities (usually a non-templated base class).

Does this mean that the Rust frontend is spitting out the intermediate representation such that it doesn't allow the de-duplication at the linking phase? I see that Rust now has its own linker as of Sep 25' but it still works with normal linkers that are used in C and C++ too - gnu ld, lld, mold, ...

AnimalMuppet|3 days ago

I think the question is, do you know at compile time what the concrete type is? In situations where you do, use static. (I'm not sure I'd call that "polymorphism". If you know the static type it's just a function on a type, and who cares that other types have functions with the same name?) But if you don't know the concrete type at compile time, then you must use dynamic dispatch.

And you can use each approach with the same type at different points in the code - even for the same function. It just depends on you local knowledge of the concrete type.

menaerus|2 days ago

That's polymorhpism 101, and not quite what I was asking. From my understanding what Rust has is something different to what C++ is offering. In C++ you either opt-in for static- or dynamic-dispatch. In Rust it seems that you can mix both for the same type object and convert between the two during runtime. It seems that this is true according to the example from dminik from the comment above but the actual purpose is still not quite evident to me. It seems that it tries to solve the problem of excessive template instantiations, called as I can see monomorphizations in Rust. In C++ this is normally and mostly done through the linker optimizations which may suggest that Rust doesn't have them yet implemented or that there are more useful cases for it.