I have a stylistic question. I have some types that implement a Lookup trait (see linked example) among some other traits. The lookup methods takes an immutable reference to self because in this application data structures on which lookups can be performed are immutable. There is however one exception, which is a very basic lookup table that automatically generates new identifiers for strings that are not known to the lookup table. Since this is the exception rather than the rule, I don't want to change the trait to take &mut self, since this would cascade up to many methods that call lookup. So, the most logical solution in this situation is to use interior mutability in this particular data type.
However, now I would like two variants of the same basic data type: one that mutates and one that doesn't. I don't want a setter method to change the mutability, because then immutability cannot be enforced through the type system. The most straightforward solution is to make them two separate data types. However, this would entail code duplication, since all the other implemented traits are the same.
I thought of another 'clever' solution where I use a type parameter to mark whether the type is mutable or immutable and use different trait implementations for the Lookup trait based on this type parameter. Here is a simplified example:
I was wondering whether you think this is a sane solution or unnecessarily complicated.
I think it’s sane but I would split them into separate types to avoid the RefCell in the immutable case . To avoid duplication, maybe use a macro to common out the boilerplate.
If you stick to the current design, change your marker structs to be empty enums to cement their uninhabitedness.
If you go with your solution, you really should make the Immutable and Mutable traits private and expose only the two structs as newtypes, e.g. pub struct MutableTable(LookupTable<Mutable>);.
You can also do the following:
Create a struct with lookup_const and lookup_mut inherent methods
Implement Lookup for it trivially using lookup_const
Create a newtype wrapper over RefCell<LookupTable>
I'd say this looks more clean. The immutable version doesn't even know about the mutable version (and doesn't use RefCell unlike the original code), and the mutable version just adds a RefCell and reuses a public lookup_mut method.
Another option might be to make Lookup an associated type on your other main trait(s). Then implement it for HashMap<String, usize> and RefCell<HashMap<String, usize>> and have those other traits pick the type they want to use. That way you abstract out the lookup without having to duplicate the other traits.
I considered using a wrapper type/composition. However, this has the downside that every other trait needs to be implemented on the wrapper type as well, whilst in my implementation only the Lookup trait needs to be implemented twice.
But indeed, using a wrapper type has the large benefit of avoiding RefCel in the immutable variant and the other traits implementations are trivial. Thanks for the idea, I might go for it!
I found another solution that is closer to my original idea. I still parametrize LookupTable over Mutable/Immutable, however now Mutable/Immutable hold a RefCel<HashMap<String, usize>/HashMap<String, usize>. So, LookupTable will not use a RefCel for the immutable variant. Then there are two possiblities:
Let Mutable/Immutable implement Lookup and the implementation of Lookup lookup in LookupTable would simply defer to these.
Let LookupTable implement Lookup with different implementations for Mutable and Immutable (this is what I did on the example below).