Is stealthily generated dynamic dispatch a thing?

A group of non-Rust developers where discussing Rust performance, and one of them claimed that Rust traits don't have static dispatch, which is wrong (can be trivially disproved on godbolt). Later they modified the claim and said Rust doesn't have "guaranteed" static dispatch.

I didn't want to get involved so I didn't ask -- but I'm curious if there some actual lore behind this. Is there, or has there been, some case where Rust non-obviously might sneak in a vtable into code that looks like it should use static dispatch?

If you don't explicitly use the dyn keyword, there won't be dynamic dispatch. In fact, the complier might even be able to optimize dynamic dispatch into static dispatch. They might refer to cases where you don't use dyn yourself, but your dependencies do so without you knowing. Or cases where type inference hides the fact that dynamic dispatch is used. From a paper I just found while searching for references:

Dynamic trait objects are used throughout the Rust standard library, so even if programmers do not opt-in to dynamic dispatch within their own source code, they are likely to pull in Rust source that constructs and uses vtables

5 Likes

A couple of notes on this:

  • It's important to not assume that static dispatch is always better for performance. Dynamic dispatch — or more broadly, avoiding inlining and monomorphization — can reduce the amount of machine code produced and executed, allowing more of the program’s machine code to fit in CPU cache at once. For example, std uses dynamic dispatch for implementing the formatting system — this is, I believe, mostly to minimize compile time and binary size, but it’s also unlikely that a version of println!("{my_complex_data_structure:?}") would run better if the compiler inlined the specific code for writing to stdout into every single one of the impl fmt::Debug for ... implementations involved in printing that data structure.

  • You can be sure a particular trait doesn't have a vtable and won’t be dynamically dispatched if it has features that are incompatible with dynamic dispatch, like a generic method (that doesn't have a where Self: Sized bound).

5 Likes

Note that on older editions this is not true since you can omit the dyn keyword.

But even then what determined whether the compiler would use static or dynamic dispatch depended on the syntactic position the trait was used in/the type the trait method was called on.

3 Likes

Right, I should've said as long as no trait objects are involved. My memories of the pre-2021-Edition times are getting a bit hazy :sweat_smile: