-3

I want the enum to act like a variant with the struct I previously defined:

pub struct Element {
    symbol: String,
    atomic_number: u8,
    atomic_mass: f32,
}
pub struct Hydrogen {
    element: Element,
}
pub struct Helium {
    element: Element,
}
pub struct Lithium {
    element: Element,
}
pub enum ElementKind {
    HYDROGEN(Hydrogen),
    HELIUM(Helium),
    LITHIUM(Lithium,
}

impl Default for Hydrogen {
    fn default() -> Self {
        Hydrogen {
            element: Element {
                symbol: "H".to_string(),
                atomic_number: 1,
                atomic_mass: 1.008,
            },
        }
    }
}

impl Default for Helium {
    fn default() -> Self {
        Helium {
            element: Element {
                symbol: "He".to_string(),
                atomic_number: 2,
                atomic_mass: 4.003,
            },
        }
    }
}

impl Default for Lithium {
    fn default() -> Self {
        Lithium {
            element: Element {
                symbol: "Li".to_string(),
                atomic_number: 3,
                atomic_mass: 6.491,
            },
        }
    }
}

fn main() {
    let e = ElementKind::HYDROGEN;
    match e {
        // TODO
    }
}

What is the correct way to write the match statement in order to, for example, always print out the symbol of the element?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64
  • 2
    What do you expect `element` to be? You never declare a variable with that name. It looks like you may be mixing up the variant `ElementKind::Hydrogen` with the structure `Hydrogen`. – Brian61354270 Mar 18 '20 at 17:59
  • Perhaps it should be `e.symbol`? – Hristo Iliev Mar 18 '20 at 18:00
  • Where do you believe that you are creating a value of type `Hydrogen`? – Shepmaster Mar 18 '20 at 18:01
  • @HristoIliev `e` is an `ElementKind`, not an `Element`. It has no data members. – Brian61354270 Mar 18 '20 at 18:02
  • @Shepmaster in the line with let, just before the match statement – nyarlathotep108 Mar 18 '20 at 18:04
  • 1
    @nyarlathotep108 See my previous comments. `ElementKind::Hydrogen` is a different type than `Hydrogen`. – Brian61354270 Mar 18 '20 at 18:05
  • Guys, I think you got the problem and what I was trying to achieve, the downvotes are seriously out of place. Thanks. – nyarlathotep108 Mar 18 '20 at 18:07
  • @Brian I want the enum to act like a variant with the struct I previously/elsewhere defined, you're right I probabl have used wrong syntax and in fact redefined new types with same name but inside enum scope. – nyarlathotep108 Mar 18 '20 at 18:11
  • 2
    Your question might be answered by the answers of [Is there a way to use existing structs as enum variants?](https://stackoverflow.com/q/49705007/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Mar 18 '20 at 18:14
  • @Shepmaster I think the question is different, but I kinda see the solution to my problem in the code of that question. I'll try it now. Thanks. – nyarlathotep108 Mar 18 '20 at 18:16

1 Answers1

3

When you write

struct Foo {
    foo: u32,
}

enum Bar {
    Foo,
}

there is absolutely no relationship between the Foo structure and the Bar::Foo variant. They just happen to have the same name. (See also Is there a way to use existing structs as enum variants?).

The usual ways to solve this would be to have a field in the variant:

enum Bar {
    // The first Foo is the variant name, the second Foo a field of type Foo
    Foo(Foo),
}

or to inline the structure in the enumeration (that is, use a struct variant):

enum Bar {
    Foo {
        foo: u32,
    }
}

However in your case I believe that you do not need to create a structure per element and can just do the following:

#[derive(Debug)]
pub struct Element {
    symbol: String,
    atomic_number: u8,
    atomic_mass: f32,
}

pub enum ElementKind {
    Hydrogen,
    Helium,
    Lithium,
}

impl From<ElementKind> for Element {
    fn from(e: ElementKind) -> Element {
        use ElementKind::*;

        match e {
            Hydrogen => Element {
                symbol: "H".to_string(),
                atomic_number: 1,
                atomic_mass: 1.008,
            },
            Helium => Element {
                symbol: "He".to_string(),
                atomic_number: 2,
                atomic_mass: 4.003,
            },
            Lithium => Element {
                symbol: "Li".to_string(),
                atomic_number: 3,
                atomic_mass: 6.491,
            },
        }
    }
}

fn main() {
    let e = ElementKind::Hydrogen;
    println!("{:#?}", Element::from(e));
}

(Permalink to the playground)

Indeed, with your solution, every instance of ElementKind would hold redundant information: the variant itself (which is enough to identify an element), and its data (which is also enough to identify an element).

mcarton
  • 27,633
  • 5
  • 85
  • 95
  • Yes I was kinda trying to have some mutually exclusive constants in an `enum`, much like Java `enum` classes, which have fields, but using `variant` / `tagged union` approach since Rust does not really have `enum` classes... messy indeed. Your solution is much cleaner. I will look into this `From` trait. – nyarlathotep108 Mar 18 '20 at 18:42
  • 1
    Rust enums are far more powerful that Java's. Java enums are just glorified constants. Fields don't change that. [Here is a Rust example similar to using Java's enum with fields](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9c0b1dcef61a8237a38fa20e1f7fbee7). – mcarton Mar 18 '20 at 18:56
  • I agree in general, but for example in the code you linked you don't really represent the mutual exclusion between those types, and you can still construct an element with random values in its fields. – nyarlathotep108 Mar 18 '20 at 19:16
  • Make your constructor private then. – mcarton Mar 18 '20 at 19:19
  • I think is it still slightly not the same. What I think is missing in the language is, given an `enum` whose fields are all of the same `struct` type, to be able to initialize those `enum` fields with values. This is possible when the `enum` fields are trivial integers, but apparently it is not for `struct`. – nyarlathotep108 Mar 19 '20 at 07:15
  • You can `Derive` or if the case is complicated `impl` the `Default` trait for each struct in your enum and then `Derive` the default implementation for the enum. https://doc.rust-lang.org/std/default/trait.Default.html – Danny Meyer Mar 20 '20 at 19:24