2.17 Enums

A Rust enum is a type representing one type of data contained by the enum itself. In turn, there may be different types of data contained by the enum. Let us start off with a simple example:

enum CarEngineState
{
	On, 
	Off
}

This enum describes the state of a car engine, and tells us whether it is on or off. Let us fill in our main function with some code and make use of this enum:

let mut state = CarEngineState::Off;
let turn_on = true;

// Same as turn_on == true (true evaluates to true, so does turn_on == true)
// The body of the ‘if’ will thus run
if turn_on
{
	state = CarEngineState::On;

	println!("You successfully turn on the car engine.");
}

In this example we create a binding named state which we bind to a new instance of our freshly created enum CarEngineState. The state binding has the same type of our enum and may contain any of the versions of the enum we created it with.

Whether a binding describing whether we should turn on the engine or leave it off is true or false, we either turn on the car engine by changing state to a different version of CarEngineState. We access the different versions of our enum using :: followed by the desired type of the enum.

Alternatives

The different versions of an enum we provide it with, are defined in a struct-like manner. This means that we can provide the enum with different amounts of information depending on what alternate version of our enum we choose to use:

enum CarEngineState
{
	On { turns_on_car_alarm: bool }, 
	Off
}

The On version of our enum now has an additional condition that one may specify upon creating that version of CarEngineState. Off serves as if a unit-like struct, containing no information, acting as its own usit in and of itself.

Had we wanted to, we could have used ‘tuple structs’ or anything else from the struct serie of types. Let us take advantage of this newly added information and edit our main:

let mut state: CarEngineState = CarEngineState::Off;
let turn_on = true;

if turn_on
{
	state = CarEngineState::On { turns_on_car_alarm: true };

	println!("You successfully turn on the car engine.");
	
	// Next piece of code goes here
}

We now tell the binding the additional information required by the enum that we wish for the car alarm to go off upon having started our engine. But what if we wanted to print out a message being confused as to why the car alarm all of a sudden went off? For that, we may use match in order to destructure the enum depending on what type it is.

Match

Let us destructure that enum and print out a silly message if the car alarm goes off:


match state
{
	CarEngineState::On { turns_on_car_alarm: x } => {
		if x
		{
			println!("For some odd reason the car alarm goes off.");
		}
	}, 
	CarEngineState::Off => {}
}

We decide that it is state that we wish to match. From there we need to provide the match with all the different versions of CarEngineState, in order for the match to know we are serious about destructuring this enum. On the line where we match the On version of our enum, we also tell the match that we would like to access turns_on_car_alarm using the name x. We then provide it with a block explaining what will happen upon state being of the version On: we check if turns_on_car_alarm is set to true, in which case we print out a message explaining the odd scenario where the car alarm goes off turning on our car engine. Stange Days Indeed.

This can feel somewhat akward at times, having to cover all the different patterns that may be inside the match. Lucky for us, there is an easier way of destructuring an enum on the spot.

If let

There is something in Rust called an if let: this is using if and let at the same time, to check whether or not an enum is in the style of a certain one of its versions. Rather than using the previous piece of code to handle the car alarm going off, we will modify it into the following:

if let CarEngineState::On { turns_on_car_alarm: x } = state
{
	if x
	{
		println!("For some odd reason the car alarm goes off.");
	}
}

This dereferences the state of a car engine and enters the scope of the if if the CarEngineState state is of the version On. We then check to see if turns_on_car_alarm is true, and if it is print out the same message as in the previous example using match.

While let

This is another version of the if let which, rather than just dereferencing the enum for us, loops for as long as the enum is of a certain type. Consider this:

while let CarEngineState::On { turns_on_car_alarm: x } = state
{
	if x
	{
		println!("For some odd reason the car alarm goes off.");
	}
	else
	{
		println!("For some odd reason the car alarm does not go off. How utterly strange.");
	}
}

Although not particularly useful for this specific program, while let is seriously useful at times. Use it as you see fit.

Exercises

  • Create an enum for a person, its alternate versions should contain both man and woman, as well as have some values associated with them.
  • Print out some information about the two different versions of your Person enum.

Conclusion

enums are all over Rust’s standard library, and are really handy when coming across certain tasks. There is, for instance, one called Option that either contains a value or does not at all. This certain enum may be used as an alternative to null contained by some other languages.