As a project to get familiar with Rust I wrote a regular expression search. (for which I got a lot of help on the code review forum; thanks!) It is now finished and working, but I am rethinking some of the design. It uses structs representing different types of nodes, wrapping them in enums rather than Boxes. I decided to refactor, replacing the Node enum with a trait and dealing with the structs directly rather than encased in the enum.
It is not really practical to use the trait for all access, that would require lots of methods which would do nothing on most types, so I want to recover the struct from the trait. I found this can be done with the Any trait, but as I was looking into it I saw How to match trait implementors on Stack Overflow, where the accepted answer says
You have two options here. If you know the set of structures implementing your trait in advance, just use enum, it's a perfect tool for this. They allow you to statically match on a closed set of variants:...This is by far the simplest way and it should always be preferred.
Is this the accepted view? From the beginning I thought traits seemed the natural way to do this but decided since my inexperience made it tougher I'd use enums and then see if it could be replaced with traits. I think I see my way through now to doing that, but it requires accessing the full structure from the trait. It can be done, but my question is should it, or is the code better left as it is?
Or is adding lots of methods to the trait, supplying default versions doing nothing, and overriding them only for the structs that use them?
Another thought I had was using unions - in that case accessing the objects is simple (as long as I know the type), but I get the feeling unions are supplied mainly as an aid to interacting with other languages, and are discouraged in regular Rust coding. Certainly they add back some of the danger that Rust is designed to remove from programming.
So that is 4 possible designs:
- enums wrapping structs (leave it as it is
- traits, somehow recovering the original struct from the trait
- traits, adding lots of methods that only exist on one struct and do nothing on the others
- unions
Is there a strong preference for any of these designs? Any that are clearly recommended against?