Rust-- Enum, Option, if let, while let

160 阅读4分钟

Enum

其他语言中也有枚举类型,但rust中的Enum还有一些额外的特性,使得它更强大

先从最基础的用法开始,定义一个Payment枚举类型

enum Payment {
    Cash,
    CreditCard,
}

fn main() {
    let some_payment = Payment::Cash;

    match some_payment {
        Payment::Cash => {
            println!("Paying with cash...");
        },
        Payment::CreditCard => {
            println!("Paying with credit card...");
        },
    }

}

// 此时的用法很简单,来自其他编程语言背景的人,也会感觉很熟悉
// 但如果此时我对enum Payment中加入新的枚举项,会立刻被compiler发现,并提示你match没有考虑到全部的情况


enum Payment {
    Cash,
    CreditCard,
    DebitCard, // 加入 借记卡 方式
}

fn main() {
    let some_payment = Payment::Cash;

    match some_payment {
        Payment::Cash => {
            println!("Paying with cash...");
        },
        Payment::CreditCard => {
            println!("Paying with credit card...");
        },
    }

}

编译器提示,`DebitCard` not covered, 可以看出rust编译器的提示非常详细,是初学rust的好帮手

error[E0004]: non-exhaustive patterns: `DebitCard` not covered
  --> src/main.rs:10:11
   |
1  | / enum Payment {
2  | |     Cash,
3  | |     CreditCard,
4  | |     DebitCard, // 加入 借记卡 方式
   | |     --------- not covered
5  | | }
   | |_- `Payment` defined here
...
10 |       match some_payment {
   |             ^^^^^^^^^^^^ pattern `DebitCard` not covered
   |
   = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
   = note: the matched value is of type `Payment`


以上是基本用法,rust中enum的强大之处在于,enum中的每个枚举项,还可以关联具体的数据

// 对enum item Cash关联 f32 类型的值

enum Payment {
    Cash(f32), // 关联 f32 类型的值
    CreditCard,
    DebitCard,
}

fn main() {
    let some_payment = Payment::Cash(100.); // 定义时,需要指定f32的值

    match some_payment {
        // compiler知道 amount是 f32 类型
        Payment::Cash(amount) => {
            println!("Paying with cash in the amount of {}", amount);
        },
        Payment::CreditCard => {
            println!("Paying with credit card...");
        },
        Payment::DebitCard => {
            println!("Paying with debit card...");
        },
    }

}


不光能关联简单的类型,如上例中的f32, 还可以关联更复杂的类型,比如tuple,struct

// 方法2和3更清晰,每项都有清楚的业务含义
enum Payment {
    Cash(f32), 
    CreditCard(String, f32), // 定义方法1: 使用tuple
    DebitCard(DebitData),  // 定义方法2: 使用struct
    Crypto{account_id: String, amount: f32}, // 定义方法3: 类似于struct
}

struct DebitData {
    pub card_number: String,
    pub amount: f32,
}

fn main() {
    let some_payment = Payment::Cash(100.); 

    match some_payment {
        Payment::Cash(amount) => {
            println!("Paying with cash in the amount of {}", amount);
        },
        Payment::CreditCard(some_string, some_f32) => {
            println!("Paying with credit card...some_string {}, some_f32 {}", some_string, some_f32);
        },
        Payment::DebitCard(data) => {
            println!("Paying with debit ... card_number is {}, amount {}", data.card_number, data.amount);
        },
        Payment::Crypto{account_id, amount} => {
            println!("Paying with crypto... account_id is {}, amount {}", account_id, amount);
        },
    }

}

// 此时因为我们在main()函数中,只定义了 Payment::Cash, Payment的其他项都未被使用,所以compiler会提出这样的警告: `variant is never constructed: `CreditCard``
// 解决办法,可以在暂时不用的enum item前加一个_ (下划线),告知rust compiler,我自己知道,我暂时不用
// 此时编译,会发现只有 DebitCard 和 Crypto 两项提出警告

enum Payment {
    Cash(f32), 
    _CreditCard(String, f32), // 加下划线
    DebitCard(DebitData),  
    Crypto{account_id: String, amount: f32},
}

struct DebitData {
    pub card_number: String,
    pub amount: f32,
}

fn main() {
    let some_payment = Payment::Cash(100.); // 定义时,需要指定f32的值

    match some_payment {
        Payment::Cash(amount) => {
            println!("Paying with cash in the amount of {}", amount);
        },
        // 此处也需要 下划线
        Payment::_CreditCard(some_string, some_f32) => {
            println!("Paying with credit card...some_string {}, some_f32 {}", some_string, some_f32);
        },
        Payment::DebitCard(data) => {
            println!("Paying with debit ... card_number is {}, amount {}", data.card_number, data.amount);
        },
        Payment::Crypto{account_id, amount} => {
            println!("Paying with crypto... account_id is {}, amount {}", account_id, amount);
        },
    }

}

我们将处理payment的逻辑写成一个函数,重新组织之后的代码如下:

// 方法2和3更清晰,每项都有清楚的业务含义
#[derive(Debug)]
enum Payment {
    Cash(f32), 
    CreditCard(String, f32), // 定义方法1: 使用tuple
    DebitCard(DebitData),  // 定义方法2: 使用struct
    Crypto{account_id: String, amount: f32}, // 定义方法3: 类似于struct
}

#[derive(Debug)]
struct DebitData {
    pub card_number: String,
    pub amount: f32,
}

fn process_payment(some_payment: Payment) {
    match some_payment {
        Payment::Cash(amount) => {
            println!("Paying with cash in the amount of {}", amount);
        },
        Payment::CreditCard(some_string, _) => {
            println!("Paying with credit card...some_string {}", some_string);
        },
        Payment::DebitCard(data) => {
            println!("Paying with debit ... card_number is {}, amount {}", data.card_number, data.amount);
        },
        Payment::Crypto{account_id, amount} => {
            println!("Paying with crypto... account_id is {}, amount {}", account_id, amount);
        },
    }
}


fn main() {
    let some_payment = Payment::Cash(100.); 
    process_payment(some_payment);
    
    let cc_payment = Payment::CreditCard("CC Num".to_string(), 250.);
    process_payment(cc_payment);
    
    let debit_payment = Payment::DebitCard(DebitData{
        card_number: "Debit Num".to_string(),
        amount: 400.,
    });
    process_payment(debit_payment);

    let crypto_payment = Payment::Crypto{
        account_id: "abc 123".to_string(),
        amount: 1000.,
    };
    process_payment(crypto_payment);


}


Option

Option<T> 是一种常见的 Enum, Option只有两种可能,分别是 Some和None

pattern matching 可以用在普通变量、函数的返回值, tuple, enum, struct上, 还可以搭配if, while使用, 即 if let 语法 和 while let语法

// match 的各个arm中, 使用if
let pair: (i32, i32) = (2, -2);

match pair {
    (x, y) if x == y => println!("x == y"),
    (x, y) if x + y == 0 => println!("x + y == 0"),
    (x, _) if x % 2 == 1 => println!("x is odd"),
    _ => println!("default case"),

}
let x: Option<i32> = None; // Some(5)

match x {
    Some(v) => println!("Matched {}", v),
    None => println!("Default case, x = {:?}", x),
}

// 有时候,上面这种写法太繁琐,就会用到 if let 语法
// 本质上也是一种 pattern matching (但不需要穷举所有情况)
// 不像普通的if 后面表达式求值为一个bool
if let Some(v) = x {
    println!("Matched {}", v); //注意是分号
} else {
    println!("Default case, x = {:?}", x);
}

// 上面的 if let看起来跟原始的match相比,并没有简化
// 实际上 if let可以不跟else使用,而不引起编译器报错,但match必须穷举完所有的情况
if let Some(v) = x {
    println!("Matched {}", v);
} 

// while let 语法
// while let 也是一种 pattern matching
// 不像普通的while后面跟着一个bool 表达式

fn main() {
    
    let mut count: Option<i32> = Some(0); // mutable类型,后面会修改

    while let Some(v) = count {
        if v > 10 {
            count = None;
        } else {
            println!("Count is {}", v);
            count = Some(v + 1);
        }
    }

}

参考 [1] www.youtube.com/watch?v=N28…