Earlier quoted context omitted.
Your concerns are not unwarranted. I've done several Rust projects and usually there really isn't any trade-off. If you want something open for extension, use a trait unless you can prove to yourself that you "can't". If it's closed or unlikely to be extended, use an enum. Think of it this way: In Rust you have a choice between open and closed (trait vs enum). In Java, you get open (interface) and that's it. Other la…
This is an excellent answer, thank you. I suppose the only remaining discomfort I anticipate is if you have an internal abstraction that might one day be exposed externally in a library. That refactoring doesn’t seem fun, but I suppose these things rarely sneak up on you.
The place where this ends up happening most is around error handling. Crafting your public error types in Rust is an art form. Usually in Rust libraries, errors are implemented in enums. So whoever calls your API can see that it failed and then can match on the reasons it may have failed (or just wrap it in their own error type and bubble it up). If you're not careful, you can cause breaking changes in your API by doing something as simple as changing a dependency (your dep's concrete error type was wrapped inside one of your error variants).
I have somewhat mixed feelings on it, but Rust does allow us to mark enums with a special annotation that forces all matches on it to include a wildcard match (even if it matches all current variants explicitly). It is generally considered good practice to mark your error enums with said annotation so that you can add failure modes in the future without requiring a major version bump for just an extra error case. One could use the same annotation for any enum, of course.