> 6) If you absolutely need to use static dispatch purely for ergonomics (and not because of the performance) then create two function - a dynamically dispatched private one which accepts a &dyn T and contains the actual functionality, and a public one which accepts impl T and is a one-line wrapper around the private one.
Would you really recommend this as common advice? It seems like not a good idea to me. If all you cared about was binary size and compilation speed, maybe, but not otherwise. Same with blanketly recommending use of &dyn T instead of <T>. There are other problems with dynamic dispatch in rust, namely it's kind of a pain if you need to `+ OtherTrait` with it.
Yes I would. In general people tend to overuse static dispatch even when it's not really necessary. Of course the issue is a little bit more nuanced than "always use X unless Y" and there are tradeoffs in play here that need to be balanced.
For example, if your function is really small - yeah, it's probably fine to just use static dispatch. If you're writing a generic data structure - you most likely also want it to be a statically dispatched Struct<T>, but with a healthy dose of #[cold] annotated non-generic functions for the cold paths. However, let's say that you have a function that accepts a filesystem path and loads a PNG from it - you do not want the PNG loading code to be 1) duplicated in every compilation unit (compilation time bloat), and 2) monomorphised three times just because you passed an `&str` once to it, a `String` another time, and a `PathBuf` yet another time (compilation time and executable size bloat), so you definitely do want dynamic dispatch here (at least under the hood with the two function trick).
I do think the default should indeed be &dyn T and you should only go for impl T when you can actually clearly substantiate why you should use it, instead of the other way around which is the default now in the Rust ecosystem. (Which is how you end up with 20+ second edit-compile cycles one of the sibling comments mentioned.)
> you do not want the PNG loading code to be 1) duplicated in every compilation unit (compilation time bloat), and 2) monomorphised three times just because you passed an `&str` once to it, a `String` another time, and a `PathBuf` yet another time (compilation time and executable size bloat), so you definitely do want dynamic dispatch here (at least under the hood with the two function trick).
It appears that there is some work to (partially?) address this problem in PR #69749 [0], which, from what I can tell, aims to avoid instantiating functions on unused type parameters.
It's not a perfect solution for your case, but should be fairly easy to take advantage of.
Could a proc macro be written that uses dynamic dispatch in debug and static dispatch in release? That would be optimal for dev compilation speed and binary speed, at the cost of binary size. It seems like a pretty good tradeoff for most cases.
IIRC I think there was a crate with a procedural macro like that which did something kinda similar to this. However currently that isn't really optimal for quite a few reasons: 1) having a procedural macro by itself pulls in extra dependencies, 2) it introduces extra work for the compiler so it does negatively impact the compile times (the procedural macros don't operate directly on the AST, so they have to parse the token stream into an AST, process it, serialize it back into a raw token stream, and the compiler has to parse it again), 3) Rust's current procedural macro machinery doesn't yet support emitting proper error messages from a procedural macro, so if you'll get an error or a warning from a piece of code generated by a procedural macro it will just point to the #[name_of_the_macro] annotation instead of the actual location where the issue originated from.
Your practice deviates significantly from what other people are doing in the ecosystem, but if you like it for your personal projects, more power to you.
> Your practice deviates significantly from what other people are doing in the ecosystem
Yeah, I know, that's what I said in the last paragraph! (: And that's also why I enjoy a 2 second recompile times (I could probably go even lower with some more refactoring) in release mode (debug mode is too slow for me at runtime) for a medium sized project while other people are struggling with half a minute compile times on projects less than half of mine's size.
I'm not saying that what I wrote is the ultimate panacea, but it's certainly one way of tackling the problem of compile times and executable bloat, and it'd certainly be nice for more crates in the ecosystem to take this issue more seriously. (You can only do so much by making the compiler faster.)
Would you really recommend this as common advice? It seems like not a good idea to me. If all you cared about was binary size and compilation speed, maybe, but not otherwise. Same with blanketly recommending use of &dyn T instead of <T>. There are other problems with dynamic dispatch in rust, namely it's kind of a pain if you need to `+ OtherTrait` with it.