100% developed

Rust for the Novice Programmer/Enums

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Enums[edit | edit source]

Let's say we were storing three points which form an angle and we wanted to keep track of some properties about it in a struct. What properties would we want to keep track of in a struct? Well, we could store the three points' x and y positions, the angle between them. Let's say we wanted to store what type of angle it is too. As a reminder: acute angles are less than 90 degrees, right angles are exactly 90 degrees, obtuse angles are more than 90 but less than 180 and reflex are greater than 180. How to store these different states? One simple way might be to assign a number to each one. For example, 0 is acute, 1 is right, 2 is obtuse and 3 is reflex. Let's see how that might work:

 struct Points {
     point1: (f64, f64),
     point2: (f64, f64),
     point3: (f64, f64),
     angle: f64,
     angle_type: u8,
 }

Now we have to remember which number corresponds to which angle type. Also there is the possibility that the number is set to something that isn't a valid angle type, when the point of a type system is to ensure that values should only be things that make sense and are valid. This fails in that purpose. This is one reason for enums!

An enum(short for enumerated type) is a way of saying that a thing may be in one of a differing number of states. e.g. A player character may be in an idle, running or jumping state. An image may in an animated or static state. etc. Here, we define our enum like this:

 enum AngleType {
     Acute,
     Right,
     Obtuse,
     Reflex,
 }

Now, we can put this into our struct like this:

 struct Points {
     point1: (f64, f64),
     point2: (f64, f64),
     point3: (f64, f64),
     angle: f64,
     angle_type: AngleType,
 }

And we use it like this:

 fn main() {
     let points = Points {
          point1: (1.0, 2.0),
          point2: (1.0, 1.0),
          point3: (2.0, 1.0),
          angle: 90.0,
          angle_type: AngleType::Right,
     };
 }

Note that we have to write it out quite verbosely, explicitly saying that it is Right of type AngleType. One important thing about Rust is that sometimes it encourages writing things out quite clearly even if this does take longer to write. This is because of some rules of programming:

  1. Code will be read more times than it is written
  2. Code is easier to write than read
  3. Shorter names for types and variables often don't explain things as comprehensively as longer ones

All this means that long term it is better to take time when writing code to save time later on.

Now we can check if an enum is a type we expect with the normal == operator. e.g.

 fn is_angle_right(points: Points) -> bool {
     points.angle_type == AngleType::Right
 }

Wait, where's the return statement? And why doesn't it end with a semicolon?? Well because we often want to have short functions, it saves time if we use an implicit return, where we omit the semicolon so the statement returns automatically. The normal rules apply, so the type of the statement must match the return type of the function. This can be useful but can also sometimes be difficult to follow, so use where it makes sense in your view.

Another great thing about Rust enums is that since different states might have different data associated with them, we can store the data in these types. For example, let's say we wanted to add a boolean if the angle is aligned with the axes. This only really makes sense for the right angle, so we can add that boolean like so:

 enum AngleType {
     Acute,
     Right(bool),
     Obtuse,
     Reflex,
 }

But how do we get values out of enums? That's what pattern matching is for:

Next: Pattern Matching