← Back to list

Rust Day 10

Enums

zoolpher · 2026-02-06 14:13 · 2 claps · 5.5 min read
#enum #option-enum
Open on Medium ↗

Rust Day 10

Enums

[embed]The Rust Programming Language Let's look at a situation we might want to express in code and see why enums are useful and more appropriate than…doc.rust-lang.org

Where structs give you a way of grouping together related fields and data, like a Rectangle with its width and height, enums give you a way of saying a value is one of a possible set of values.

For example, we may want to say that Rectangle is one of a set of possible shapes that also includes Circle and Triangle.

enum Shapes {
    Rectangle,
    Circle,
    Triangel,
}

But rust docs is considering an enum of IpAdresses…..So, we work with that…

Just in case if you don’t know; Ip Address is of 2 types IpV4 and IpV6.

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(IpAddrKind::V4);
    route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

IpAddrKind is now a custom data type that we can use elsewhere in our code.

Enum Values

[embed]The Rust Programming Language Let's look at a situation we might want to express in code and see why enums are useful and more appropriate than…doc.rust-lang.org

We can create instances of each of the two variants of IpAddrKind like this:

    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

Both values IpAddrKind::V4 and IpAddrKind::V6 are of the same type: IpAddrKind .

Using enums has even more advantages. Thinking more about our IP address type, so far we don’t have a way to store the IP address data; we only know what kind it is (Ipv4 or Ipv6).

    enum IpAddrKind {
        V4,
        V6,
    }

    struct IpAddr {          
        kind: IpAddrKind,
        address: String,
    }

    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };

    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };

However, representing the same concept using just an enum is more concise: Rather than an enum inside a struct, we can put data directly into each enum variant. This new definition of the IpAddr enum says that both V4 and V6 variants will have associated String values:

fn main() {
    enum IpAddr {
        V4(String),   // We defined the type of V6 variant
        V6(String),
    }

    let home = IpAddr::V4(String::from("127.0.0.1"));

    let loopback = IpAddr::V6(String::from("::1"));
}

The name of each enum variant that we define also becomes a function that constructs an instance of the enum.

That is, IpAddr::V4() is a function call that takes a String argument and returns an instance of the IpAddr type.

We automatically get this constructor function defined as a result of defining the enum.

There’s another advantage to using an enum rather than a struct:

— Each variant can have different types and amounts of associated data. Version four IP addresses will always have four numeric components that will have values between 0 and 255. If we wanted to store V4 addresses as four u8 values but still express V6 addresses as one String value, we wouldn’t be able to with a struct. Enums handle this case with ease:

fn main() {
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    let home = IpAddr::V4(127, 0, 0, 1);

    let loopback = IpAddr::V6(String::from("::1"));
}

Let’s look at how the standard library defines IpAddr. It has the exact enum and variants that we’ve defined and used, but it embeds the address data inside the variants in the form of two different structs, which are defined differently for each variant:

#![allow(unused)]
fn main() {
struct Ipv4Addr {
    // --snip--
}

struct Ipv6Addr {
    // --snip--
}

enum IpAddr {
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}
}

NOTE:

This code illustrates that you can put any kind of data inside an enum variant: strings, numeric types, or structs, for example. You can even include another enum!

Let us look at another enum “Messsage”:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

This enum has four variants with different types:

  • Quit: Has no data associated with it at all
  • Move: Has named fields, like a struct does
  • Write: Includes a single String
  • ChangeColor: Includes three i32 values

Defining methods on enums:

Just as we’re able to define methods on structs using impl, we’re also able to define methods on enums. Here’s a method named call that we could define on our Message enum:

fn main() {
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }

    impl Message {
        fn call(&self) {
            // method body would be defined here
        }
    }

    let m = Message::Write(String::from("hello"));
    m.call();
}

The Option Enum

[embed]The Rust Programming Language Let's look at a situation we might want to express in code and see why enums are useful and more appropriate than…doc.rust-lang.org

Option, is another enum defined by the standard library. The Option type encodes the very common scenario in which a value could be something, or it could be nothing.

I suggest you read this case study about “null” in rust doc. It talks about why “null” is a bad idea:

[embed]The Rust Programming Language Let’s look at a situation we might want to express in code and see why enums are useful and more appropriate than…doc.rust-lang.org

The problem isn’t really with the concept of null but with the particular implementation. As such, Rust does not have nulls, but it does have an enum that can encode the concept of a value being present or absent. This enum is Option<T>, and it is defined by the standard library as follows:

#![allow(unused)]
fn main() {
  enum Option<T> {
      None,
      Some(T),
  }
}

The Option<T> enum is so useful that it’s even included in the prelude; you don’t need to bring it into scope explicitly.

You can use Some and None directly without the Option:: prefix.

The <T> is a generic type parameter. For now, all you need to know is that <T> means that the Some variant of the Option enum can hold one piece of data of any type, and that each concrete type that gets used in place of T makes the overall Option<T> type a different type. Here are some examples of using Option values to hold number types and char types:

fn main() {

    // Remember; we canuse variants of Option
    // without Option:: prefix
    // The 3 bold lines just above...

    let some_number = Some(5);
    let some_char = Some('e');

    let absent_number: Option<i32> = None;
}

The type of some_number is Option<i32>.

The type of some_char is Option<char>, which is a different type.

Rust can infer these types because we’ve specified a value inside the Some variant.

For absent_number, Rust requires us to annotate the overall Option type: The compiler can’t infer the type that the corresponding Some variant will hold by looking only at a None value. Here, we tell Rust that we mean for absent_number to be of type Option<i32>.

When we have a Some value, we know that a value is present, and the value is held within the Some.

When we have a None value, in some sense it means the same thing as null: We don’t have a valid value.

So, why is having Option<T> any better than having null?

In short, because **Option<T> and T (where T can be any type) are different types,** the compiler won’t let us use an Option<T> value as if it were definitely a valid value.

For example, this code won’t compile, because it’s trying to add an i8 to an Option<i8>:

fn main() {
    let x: i8 = 5;
    let y: Option<i8> = Some(5);

    let sum = x + y;
}

It will throw an error :

$ cargo run
   Compiling enums v0.1.0 (file:///projects/enums)
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` implements `Add<i8>`
            `&i8` implements `Add`
            `i8` implements `Add<&i8>`
            `i8` implements `Add`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `enums` (bin "enums") due to 1 previous error

This error message means that Rust doesn’t understand how to add an i8 and an Option<i8>, because they’re different types.

In other words, you have to convert an Option<T> to a T before you can perform T operations with it.


메타데이터
post_id
42e5dff74f5c
slug
rust-day-10-42e5dff74f5c
url
https://medium.com/@zoolpher/rust-day-10-42e5dff74f5c
canonical_url
https://medium.com/@zoolpher/rust-day-10-42e5dff74f5c
author_url
https://medium.com/@zoolpher
status
ok
fetched_at
2026-07-13 06:23:13