English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Rust Enums

Enumerated classes in Rust are not as simple as in other programming languages, but they can still be used very simply:

#[derive(Debug)]

enum Book {
    Papery, Electronic
}

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

Run result:

Papery

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

If you are developing a library management system now, you need to describe the different properties of two types of books (paper books have call numbers, and electronic books only have URLs), you can add tuple properties to describe enum class members:

enum Book {
    Papery(u32),
    Electronic(String),
}
let book = Book::Papery(1001);
let ebook = Book::Electronic(String::from("url://..."));

If you want to name properties, you can use struct syntax:

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

Although it can be named in this way, please note that enum class bound properties cannot be accessed like struct fields. The method of accessing 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 handled by branch structures (similar to switch in many languages). The switch syntax is classic, but it is not supported in Rust. Many languages abandon switch because switch is prone to sequential execution problems due to forgetting to add break, and languages like Java and C# eliminate this situation through safety checks.

Rust implements branch structures through match statements. First, let's understand how to use match to handle enum types:

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

Running result:

Papery book 1001

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

match enum class example {
    Classification1 => return value expression,
    Classification2 => return value expression,
    ...
}

However, all return value expression types must be the same!

If the attached properties of an enum type are defined as tuples, a temporary name must be specified in the match block:

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

match book &123;
    Book::Papery &40;i&41; &123;
        println!( "{...", i&41;;
    },
    Book::Electronic &123; url &125; &123;
        println!( "{...", url&41;;
    }
}

The match statement can not only be used for branch selection on enum types but also for integer, floating-point, character, and string slice reference (&str) types of data. Although it is legal to use branch selection on floating-point types, it is not recommended because precision issues may lead to branch errors.

When making branch selections for non-enum types, it is necessary to handle exceptions, even if there is nothing to do in the exception case. Exceptions are indicated by an underscore _:

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

Option enum class

Option is an enum class in the Rust standard library, used to fill the gap of Rust's lack of null references.

Many languages support the existence of null (C/C++、Java), which is very convenient, but also creates a huge problem, and the inventor of null also acknowledges this, "A convenient idea has caused an accumulation 10 "loss of tens of billions".

null often gives a fatal blow to the program when developers treat everything as not null: after all, as soon as such an error occurs, the program must terminate completely.

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

Java supports null by default, but it can be restricted by the @NotNull annotation, which is a makeshift solution.

Rust does not allow null values at all at the language level, but unfortunately null can efficiently solve a few problems, so Rust introduces the Option enum class:

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

If you want to define a class that can be null, you can do it like this:

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

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

fn main() {
    let opt = Option::Some40;"Hello"&41;;
    match opt123;
        Option::Some40;something&41; &123;
            println!("{}", something&41;;
        },
        Option::None &123;
            println!("opt is nothing"&41;;
        }
    }
}

Run result:

Hello

If your variable starts as a null value, show some consideration for the compiler. How does it know what type the variable is when the value is not null?

Therefore, an empty Option must explicitly specify the type:

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

Run result:

opt is nothing

This design makes it difficult to program with null values, but that is exactly what is needed to build a stable and efficient system. Since Option is introduced by the Rust compiler by default, it can be omitted when used directly as None or Some().

Option is a special enum class that can contain value branches:

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

if let syntax

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

Run the result in the main function:

zero

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

Now let's shorten this code using if let syntax:

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

The format of if let syntax is as follows:

if let match_value = source_variable {
    Statement Block
}

You can add an else block later to handle exceptional cases.

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

It still applies to enums:

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");
    }
}