Rust Book - Chapter 6.1
Definining an Enum
An Enum gives you a way of saying a value is one of a possible set of values. We might want to say that an Apple is one of a a set of possible fruits. An enum lets us encode these possibilities as an enum.
An enum value can only be one of its variants at any time.
An enum lets us enumerate all possible variants of a type. This is where it gets its name.
Take the example of an IP Address, which can be of either version four or six, but not both at the same time. We express this concept by defining an IpAddrKind
enumeration, and listing the possible kinds of an address can be. These are the variants of the enum:
enum IpAddrKind {
V4,
V6,
}
IPAddrKind
is now a custom data type we can use in our code.
Enum Values
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
The variants of the enum are namespaced under its identifier. We use a double colon to separate the two.
Enum can encode more information
Taking the IP address example, we can encode even more information about an IP Address like its address, as well as more information.
struct Ipv4Addr {}
struct Ipv6Addr {}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
We can thus put any kind of data inside an enum variant: strings, numeric types, struct or even another enum. Enums can also define methods
Imagine we wanted to represent different types of Messages in a system, we can do this using structs like so:
struct QuitMessage; //unit struct
struct MoveMessage {x: i32, y:i32,}
struct WriteMessage(String); //tuple struct
struct ChangeColorMessage(i32, i32, i32); //tuple struct
This works, but we can't easily define a function to take any of these kinds of messages. We can express the same concept using an enum like so:
enum Message{
Quit,
Move {x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
We can thus define a function to take a Message
enum
fn handle(message_type: Messsage) {}
Option enum
The option type expresses the concept that a value could be something or nothing. Rust doesn't have the concept of null used by some languages, but it however encodes the same concept of a value being present or absent using the Option enum represented in the standard library like so:
enum Option<T> {
None, //effectively same as null in other languages
Some(T), //value is present and in the Some variant
}
Option<T>
enum is so useful, it is included in prelude: you don't need to bring it into scope explicity. You can use its variants directly without the Option::
prefix
Since
None
value, in some sense means the same things as null in other programming languages: we don't have a valid value, why is havingOption<T>
better than having null? BecauseOption<T>
andT
(wher T can be any type) are different types, the compiler won't let us use anOption<T>
value as if it were definitely a valid value.
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
}
if we run the code, we get the get the error:
error[E0277]: cannot add `Option<i8>` to `i8`
--> src/main.rs:5:17
|
5 | let sum = x + y;
| ^ no implementation for `i8 + Option<i8>`
|
= help: the trait `Add<Option<i8>>` is not implemented for `i8`
= help: the following other types implement trait `Add<Rhs>`:
<i8 as Add>
<i8 as Add<&i8>>
<&'a i8 as Add<i8>>
<&i8 as Add<&i8>>
For more information about this error, try `rustc --explain E0277`.
error: could not compile `option_enum` (bin "option_enum") due to 1 previous error
The error means that Rust doesn't understand how to add an i8
and an Option<i8>
, because they're different types. When we have a type like i8
, the Rust compiler will check that we have a valid value. We can proceed confidently without having to check for null before using that value. Only when we have an Option<T>
do we have to worry about the possibility of not having a value, and the compiler will also make sure that we handle that caseu before using the value. This work is offloaded to the compiler.
You have to convert an Option<T>
to a T
before you can perform T
operations with it.
Member discussion