Question about enums

Here is a simplified example of my problem:

struct User;
struct Community;

enum Actor {
    User(User),
    Community(Community),
}

trait Name {
    fn name() -> &'static str;
}

impl Name for User {
    fn name() -> &'static str { "/u/nutomic" }
}

impl Name for Community {
    fn name() -> &'static str { "/c/rust " }
}

fn main() {
    let actor = Actor::Community(Community);
    println!("{}", actor.name());
}

Playground link

As you may have noticed, this doesnt compile. The only solution I can think of is with a match. I want to avoid that because I have an enum with 30+ variants, and a trait with multiple methods. So that would be a huge match statement in each method, when the compiler should know that the enum variants all have the same trait.

So, do you know any way to make this work without match?

@nutomic
mod
admin
creator
link
3
edit-2
2M

Update, after some more searching I found a crate which does almost what I want: impl-enum. Okay actually I want to specify a whole trait instead of individual methods, so I’ll try to make a PR for that.

Dessalines
admin
link
23M

You are right, if you are using an enum for tons of different types, you pretty much have no choice but to use a match.

Actor to me there shouldn’t be an enum, but a parent trait or struct, that all of those implement, with a name function.

@nutomic
mod
admin
creator
link
13M

The reason I am using an enum is because I am deserializing this data with serde, which works perfectly as I only need to call serde_json::from_str() once. Without the enum, I would have to execute it once for every struct. In another case I am using the same pattern to deserialize into 30+ different structs, so I would need to call serde_json::from_str() 30+ times in the worst case, until I try the right struct.

I guess generating the match with a macro is the best solution.

@nogerine
link
23M

If you don’t care whether the public-facing type is a struct or an enum, you could make the enum private and use #[serde(from="PrivateEnum")] on the public struct.

@nutomic
mod
admin
creator
link
23M

I dont care what the public type is, so that sounds perfect. But its not clear to me from the documentation how that attribute works. Do you happen to know an example?

@brombek
link
23M

Just implement Name for Actor.

Ephera
link
13M

That requires using a match statement in the implementation, though, does it not?

@brombek
link
12M

Yes, match is how you access enum variants in Rust. You only need to implement it once for the Actor enum.

Ephera
link
12M

Yeah, but the question was about not having to use such a match statement or somehow making it less verbose, since they have 30 enum variants and several methods in that trait, so the file that implements the trait would become very long.

@brombek
link
22M

The only way to destructure an enum is via match or if let statements. If all enum variants would contain common data then I would suggest to move this data to a struct and keep the enum as separate field in that structure (same like io::Error and it’s Kind enumeration). Another way would be to use a macro to derive the implementation automatically; there may be crates already there that can do this sort of delegation. See https://crates.io/search?q=delegate

Enum variants are values and not types. You cannot implement traits for values. Rust will not magically implement traits for you (unless they are auto-traits, and this currently are not user defined). Rust does not support inheritance (it is not an OOP language) and instead you use composition which means that you have to compose things manually (or via macro) if you use static constructs like the enum (in contrast to run-time trait object).

@brombek
link
22M

In this case you may want to use a trait object instead of an enum.

Ephera
link
23M

Hmm, best I can think of is switching to a whole different paradigm, akin to the Entity-Component-System architecture, but that is a pretty big step and you give up lots of compile time guarantees. So, unless you need lots of flexibility anyways, it’s probably not worth it.

Here’s a rough example thrown together from code I had laying around: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e91ae7eeff8b79e9e37c7e7917bda5c6

@nutomic
mod
admin
creator
link
23M

Thanks, but the compile time guarantees are exactly why I want to use this pattern. And honestly your code looks much more complicated than the other option, which is using a macro to generate the match.

Ephera
link
13M

I guess, this isn’t really directly a solution to your issue, it just kind of feels like an ECS-like solution might be appropriate to obsolete these types of problems completely.

But yeah, I don’t know your code and as I said, an ECS architecture can really be a pain, too.

@pingveno
link
23M

I remember seeing a crate somewhere where it implements a trait if all of the variants are tuple struct variants (wrong name) that implement that trait. Not sure what it’s called.

@nutomic
mod
admin
creator
link
13M

Maybe this one? Unfortunately it doesnt work in my case, because I have a bunch of derived traits, generics and async. Maybe a new derive trait could be written to solve this problem.

@pingveno
link
23M

Yeah, pretty sure it was that one. Alas.

Rust Programming
!rust
    • 0 users online
    • 1 user / day
    • 1 user / week
    • 7 users / month
    • 53 users / 6 months
    • 1220 subscribers
    • 279 Posts
    • 312 Comments
    • Modlog