Rust Enums

Enums in Rust are not as simple as the concept in other programming languages, but they can still be used very simply:

Example

#[derive(Debug)]

enum Book {
    Papery, Electronic
}

fn main() {
    let book = Book::Papery;
    println!("{:?}", book);
}

Output:

Papery

Books are divided into paper books (Papery book) and electronic books (Electronic book).

If you are now developing a library management system and need to describe the different attributes of the two types of books (paper books have a call number, e-books only have a URL), you can add tuple attribute descriptions to the enum members:

enum Book {
    Papery(u32),
    Electronic(String),
}

let book = Book::Papery(1001);
let ebook = Book::Electronic(String::from("url://..."));

If you want to name the attributes, you can use struct syntax:

enum Book {
    Papery { index: u32 },
    Electronic { url: String },
}
let book = Book::Papery{index: 1001};

Although you can name them this way, note that you cannot access the attributes bound to an enum variant like you access struct fields. The way to access them is in the match syntax.

match Syntax

The purpose of enums is to classify a certain type of thing, and the purpose of classification is to describe different situations. Based on this principle, enum types are often ultimately handled by branching structures (switch in many languages). The switch syntax is classic, but it is not supported in Rust. The reason many languages abandon switch is that switch is prone to fall-through issues caused by forgetting to add break. Languages like Java and C# prevent this situation through safety checks.

Rust implements branching structures through the match statement. Let's first see how to use match to handle enums:

Example

fn main() {
    enum Book {
        Papery {index: u32},
        Electronic {url: String},
    }
   
    let book = Book::Papery{index: 1001};
    let ebook = Book::Electronic{url: String::from("url...")};
   
    match book {
        Book::Papery { index } => {
            println!("Papery book {}", index);
        },
        Book::Electronic { url } => {
            println!("E-book {}", url);
        }
    }
}

Output:

Papery book 1001

A match block can also be treated as a function expression, and it can have a return value:

match 枚举类实例 {
    分类1 => 返回值表达式,
    分类2 => 返回值表达式,
    ...
}

But the types of all return value expressions must be the same!

If the additional attributes of an enum are defined as a tuple, you need to temporarily specify a name in the match block:

Example

enum Book {
    Papery(u32),
    Electronic {url: String},
}
let book = Book::Papery(1001);

match book {
    Book::Papery(i) => {
        println!("{}", i);
    },
    Book::Electronic { url } => {
        println!("{}", url);
    }
}

In addition to branching on enums, match can also branch on data of types such as integers, floating-point numbers, characters, and string slice references (&str). Among them, branching on floating-point numbers is legal, but it is not recommended because precision issues may cause branch errors.

When branching on non-enum types, you must handle the catch-all case, even if there is nothing to do in the catch-all case..The catch-all case uses an underscore_to represent:

Example

fn main() {
    let t = "abc";
    match t {
        "abc" => println!("Yes"),
        _ => {},
    }
}

Option Enum

Option is an enum in the Rust standard library. This type is used to fill the gap that Rust does not support null references.

Many languages support the existence of null (C/C++, Java), which is convenient, but it also creates huge problems. The inventor of null also admitted this, "a convenient idea caused cumulative losses of one billion dollars."

null often deals a fatal blow to the program when developers treat everything as not null: after all, as long as one such error occurs, the program's execution must completely terminate.

To solve this problem, many languages do not allow null by default, but support the appearance of null at the language level (often modified with a ? symbol before the type).

Java supports null by default, but can restrict the appearance of null through the @NotNull annotation. This is a way to cope.

Rust completely disallows null values at the language level, but unfortunately null can efficiently solve a small number of problems, so Rust introduced the Option enum:

enum Option<T> {
    Some(T),
    None,
}

If you want to define a type that can be null, you can do this:

let opt = Option::Some("Hello");

If you want to perform some operations on opt, you must first determine whether it isOption::None:

Example

fn main() {
    let opt = Option::Some("Hello");
    match opt {
        Option::Some(something) => {
            println!("{}", something);
        },
        Option::None => {
            println!("opt is nothing");
        }
    }
}

Output:

Hello

If your variable is initially null, consider the compiler: how does it know what type the variable is when the value is not null?

Therefore, an Option with an initial null value must have an explicit type:

Example

fn main() {
    let opt: Option<&str> = Option::None;
    match opt {
        Option::Some(something) => {
            println!("{}", something);
        },
        Option::None => {
            println!("opt is nothing");
        }
    }
}

Output:

opt is nothing

This design makes programming with null values more difficult, but this is exactly what is needed to build a stable and efficient system. Since Option is introduced by default in the Rust compiler, you can omit Option:: and directly write None or Some() when using it.

Option is a special enum type that can contain values for branch selection:

Example

fn main() {
        let t = Some(64);
        match t {
                Some(64) => println!("Yes"),
                _ => println!("No"),
        }
}

if let Syntax

Example

let i = 0;
match i {
    0 => println!("zero"),
    _ => {},
}

Put it into the main function, the output is:

zero

The purpose of this program is to determine whether i is the number 0, and if so, print zero.

Now use if let syntax to shorten this code:

let i = 0;
if let 0 = i {
    println!("zero");
}

The syntax of if let is as follows:

if let 匹配值 = 源变量 {
    语句块
}

You can add an else block afterward to handle the catch-all case.

if let syntax can be considered "syntactic sugar" for a match statement that only distinguishes two cases (syntactic sugar refers to a convenient alternative with the same underlying principle as some syntax).

It is also applicable to enums:

Example

fn main() {
    enum Book {
        Papery(u32),
        Electronic(String)
    }
    let book = Book::Electronic(String::from("url"));
    if let Book::Papery(index) = book {
        println!("Papery {}", index);
    } else {
        println!("Not papery book");
    }
}
Other Extensions