I want to create structs for each variant of an enum, there are 2 approaches:
// the first approach,
// store the actual fields in the enum variant
enum Test {
A(usize, String),
}
struct A(usize, String);
impl Into<Test> for A {
fn into(self) -> Test {
// here I have to assign each fields separately
Test(self.0, self.1)
}
}
// the second approach,
// store the struct directly in the enum variant
enum Test {
A(A),
}
struct A(usize, String);
impl Into<Test> for A {
fn into(self) -> Test {
// here I just need to move self
Test(self)
}
}
Looks like the second approach is simpler, will it be faster during the runtime? or is it the better design?
There shouldn't be any different in runtime speed.
What would the A struct be used for in the first example, other than converting into a Test? If you plan on using the struct on its own, separate from the enum, then sure, wrap the struct. Otherwise, you can just use the fields in the enum variant directly.
The first approach may allow more efficient layouts across the entire enum. The second approach allows implementing traits and inherent methods on the inner type, passing a reference to the inner type, and so on.
I'm thinking about the data layout. In the first approach, if the layout of Test::A and A are different, then may be the assignment is more costly?
That's a good point, its indeed a tradeoff, thanks for the insight!
Actually the only usage of struct A is to construct the enum Test and there won't be any methods for A.
So, as a conclusion in my case, looks like that's a tradeoff between maybe better data layout for the first approach and maybe faster assignment for the second approach.
There's another benefit to the second approach.
You can use a struct with named fields instead of a tuple struct.
This way, it might be clearer, what the values represent.
My rule of thumb is that I use the first variant for crate-internal stuff, where the library user is not concerned with the inner type and the second one - usually with a documented struct with named fields - if it is exposing an API for better documentation.
You can still document individual enum variants and their fields, just like you can with structs; there's no documentability benefit from splitting out into a separate struct, IME.
Another useful property of a separate type for the inner data (i.e. the second approach) is that
Sometimes I've found it useful to have methods that perform the analog of a downcast. This can easily be done by providing various fn into_variant(self) -> CustomResult<VariantType> methods on the wrapper enum, and can also have analogs that take &self and &mut self, for use cases where (uniquely) borrowing is more desirable.
These methods mostly have a boilerplate-cutting effect, and the resulting methods read pretty nicely.
This pattern can be made to work with the first approach, but then you'll end up returning tuples, which are just a Poor Man's Type: they work and it's possible to match on them, but tuples have no named fields, and no name for the type.
Another point in variant 2 is separation of concerns.
The struct is a self-contained and reusable container of a certain type of information and is not directly coupled to an enum.
And also, as @jjpe already mentioned, you can implement traits and methods for the struct.
Yes, yes you could also implement it for the enum, but always differentiating between the variants will just result in convoluted code.