Let's say I have I have two traits, TraitA and TraitB.
I have an enum FormatEnum with two variant A and B.
I have struct MyStruct that has various field and one of them is "format: FormatEnum".
I want to have a generic method on MyStruct, let's call it "fn process(data:T) -> Result<()>" that required a trait bound on T but that is different acording to the "format" field.
Is there a way to do that ?
Here below some kind of pseudo code to illustrate.
You would need the value of the format to be a compiler-time concept associated to your type. E.g. with a const generic, but using custom types there is still unstable
or you could use a const generic bool on stable; or you can use a type that’s sort-of “encoding” an enum-like value, possibly with a sealed trait, too. Feel free to ask for a code example if it isn’t clear what I mean by these alternatives.
Also: In the “type-level information” approach, in case you want to write some generic methods over Fmt: Format that merely need the format information at runtime, you could add a method to the Format trait to allow reifying the information as a run-time value.
Also note that, since the two .process methods are simply two unrelated methods with the same name; with none of these approaches will you be able to call .process in a generic context with an unknown generic Fmt: Format or const FORMAT: … format, even if you have a type T that implements the union of all the required traits.
Thank you. I understand the example with the bool, it's basically the same than the first one just a litle bit different. the last example with the PhantomData marker.... I am lost to be honest (I know the sealed traits pattern). So you change the enum Format to a trait Format, you impl on two empty enum FormatA and FormatB (that I guess could be unit-like struct at this point)... And then the marker: PhantomData confuses me.
How does it looks when using MyStruct ? I mean how will the user instantiate it and call process on data ?
It works quite similar to the const generics version. If there’s something you feel behaves different, feel free to give a code example, and I can address it.
The marker is just there because the rust compiler requires all generic type arguments to be used in some field’s type. The fn() -> … inside of the PhantomData is used in order to indicate that this is just a marker type and we don’t want to pretend we’re actually containing a value of type Fmt; in this case it’s probably entirely unnecessary to add the fn() -> part, but I did it out of habit.
Yes, they could be anything, but since they’re intended to be used exclusively for type-level information, it makes sense to make them as unusable as possible…
though using unit structs can have advantages, too. Imagine adding a format: Fmt argument to the constructor, then you could call it as MyStruct::new(FormatA) instead of using turbo-fish MyStruct::<FormatA>::new(). I’m not sure what the “best” approach is, choose any you like the most.
MyStruct::new(FormatA) is more ideal with what I am trying to do.
My problem is more complicated in fact. Here I gave the basic example with only two variant, but in fact I have somethink like 8 variants, 6 of them required the data to implement TraitA, and the last 2 variants required the data to implement TraitB.
What would you advise to do ?
My naive simple solution was just to required T: TraitA + TraitB but is it doable to do without that ?
If requiring T: TraitA + TraitB doesn’t create significant problems for your users, this might well be the easiest solution. Also remember that (unlike in languages without a static type system, or languages with lots of highly unsafe operations) refactoring in Rust is quite easy and fun, so you could always change your mind later.