DEV Community

Cover image for [Rust Guide] 6.1. Enums
SomeB1oody
SomeB1oody

Posted on

[Rust Guide] 6.1. Enums

6.1.1. What Is an Enum?

Enums allow us to define a type by listing all possible values. This is similar to enums in other programming languages, but Rust enums are more flexible and powerful because they can associate data and methods, similar to classes or structs in other languages.

6.1.2. Defining an Enum

For example, an IP address has only two possibilities—IPv4 and IPv6. It is either IPv4 or IPv6, so this is a great use case for an enum, because a value of an enum can only be one of its variants (all possible values of the enum).

enum IpAddrKind{
    V4,
    V6,
}
Enter fullscreen mode Exit fullscreen mode

This code uses the enum keyword to declare an enum type called IpAddrKind. It has two variants—V4 and V6—which represent IPv4 and IPv6 respectively.

6.1.3. Enum Values

Creating an enum value is very simple. The format is enum_name::variant. For example:

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
Enter fullscreen mode Exit fullscreen mode

The variants of an enum live in the namespace of the enum’s identifier, and that identifier is the name of the enum type.

We can declare a function that takes IpAddrKind as its parameter, and the value passed in can be either V4 or V6:

fn route(ip_addr: IpAddrKind) {  
    match ip_addr {  
        IpAddrKind::V4 => println!("IPv4"),  
        IpAddrKind::V6 => println!("IPv6"),  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Let’s try it out:
Complete code:

enum IpAddrKind{  
    V4,  
    V6,  
}  

fn main() {  
    let four = IpAddrKind::V4;  
    let six = IpAddrKind::V6;
    // Call the function  
    route(four);  
    route(six);  
    route(IpAddrKind::V4);  
}  

fn route(ip_addr: IpAddrKind) {  
    match ip_addr {  
        IpAddrKind::V4 => println!("IPv4"),  
        IpAddrKind::V6 => println!("IPv6"),  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Output:

IPv4
IPv6
IPv4
Enter fullscreen mode Exit fullscreen mode

6.1.3. Attaching Data to Enum Variants

An enum is a custom data type, so it can be used as the type of a field in a struct, for example:

struct IpAddr {  
    kind: IpAddrKind,  
    address: String,  
}
Enter fullscreen mode Exit fullscreen mode

The kind field in IpAddr is of type IpAddrKind and stores the network protocol; the other field, address, is of type String and stores the specific IP address.

With this struct, we can declare variables in main() that store IPv4 and IPv6 information:

fn main() {  
    let home = IpAddr {  
        kind: IpAddrKind::V4,  
        address: String::from("127.0.0.1"),  
    };  
    let loopback = IpAddr {  
        kind: IpAddrKind::V6,  
        address: String::from("::1"),  
    };  
}
Enter fullscreen mode Exit fullscreen mode

Rust allows data to be attached directly to enum variants, for example:

enum IpAddr {
    V4(String),
    V6(String),
}
Enter fullscreen mode Exit fullscreen mode

You add a type after each variant (they do not have to be the same type). Here, both V4 and V6 are followed by the String type.

The advantages of this approach are:

  • No need to use an extra struct
  • Each variant can have a different type and a different amount of associated data

For example:

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}
Enter fullscreen mode Exit fullscreen mode

An IPv4 address is actually made up of four 32-bit numbers (that is, four values that fit in u8), while IPv6 is a string, so String should be used. If we want to store a V4 address as four u8 values but still represent a V6 address as a String, we cannot use a struct. An enum handles this situation easily.

Let’s rewrite the code from 6.1.3:

enum IpAddrKind{  
    V4(u8, u8, u8, u8),  
    V6(String),  
}  

fn main() {  
    let home = IpAddrKind::V4(127, 0, 0, 1);  
    let loopback = IpAddrKind::V6(String::from("::1"));  
}
Enter fullscreen mode Exit fullscreen mode

That is indeed much shorter than the previous code.

6.1.4. IpAddr in the Standard Library

In fact, the standard library already provides an enum for IP addresses. Let’s see how the official version is written:

struct Ipv4Addr {
    // --snip--
}

struct Ipv6Addr {
    // --snip--
}

enum IpAddr {
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}
Enter fullscreen mode Exit fullscreen mode

The contents of Ipv4Addr and Ipv6Addr are not shown here, but that is not the point. The point is that this code shows that any type of data can be placed inside enum variants: for example, strings, numeric types, or structs. It can even include another enum.

6.1.5. Using Methods on Enums

The concept of methods was introduced in the previous article, so we will not go into too much detail here. Methods are defined with the impl keyword, as shown below:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
impl Message {
    fn call(&self) {
        println!("Something happens");
    }
}
fn main(){
    let m = Message::Write(String::from("hello"));
    m.call();
}
Enter fullscreen mode Exit fullscreen mode

This enum has four different variants:

  • Quit: does not carry any data.
  • Move: contains an anonymous struct.
  • Write: contains a String.
  • ChangeColor: contains three i32 values.

In main, the variable m is declared as the Write variant of the Message enum, with the String value hello attached to it. Then the call method is invoked on m, which prints Something happens.

Top comments (0)