← Back to context

Comment by menaerus

11 hours 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?

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.

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.