枚举

枚举就是允许我们列举所有可能的值来定义一个类型(概念上与TS的枚举基本没差别,实际使用会有不同)

定义枚举

IP地址枚举

  1. enum IpAddrKind{
  2. V4,
  3. V6
  4. }

enum关键字 + 枚举名 + 大括号 + 变体 (枚举里所有可能的值叫枚举的变体)

创建枚举值

  1. enum IpAddrKind{
  2. V4,
  3. V6
  4. };
  5. let four = IpAddrKind::V4;
  6. let six = IpAddrKind::V6;

枚举的变体都位于标识符的命名空间下,使用 :: 符号进行分割。

将数据附加到枚举的变体中

在rust中,允许将数据直接附加到枚举的变体中, 且附加的数据可以是任意类型。
定义方式

  1. enum IpAddrKind{
  2. V4(String),
  3. V6(String)
  4. }

优点

  • 不需要使用额外的struct
  • 每个变体都可以拥有不同的类型及关联的数据量

假设我们不使用数据附加到枚举的变体中的方式去定义一个IpAddr,示例:

  1. #[derive(Debug)]
  2. enum IpAddrKind{
  3. V4,
  4. V6
  5. }
  6. #[derive(Debug)]
  7. struct IpAddr { // 需要单独定义一个struct
  8. kind:IpAddrKind,
  9. address:String
  10. }
  11. fn main() {
  12. let home = IpAddr {
  13. kind:IpAddrKind::V4,
  14. address:String::from("127.0.0.1")
  15. };
  16. let loopback = IpAddr {
  17. kind:IpAddrKind::V6,
  18. address:String::from("::1")
  19. };
  20. println!("{:?},{:?}",home,loopback)
  21. }

将数据直接附加到枚举的变体中,实际表达内容是一致的。示例:

  1. #[derive(Debug)]
  2. enum IpAddr {
  3. V4(u8,u8,u8,u8),
  4. V6(String)
  5. }
  6. fn main() {
  7. let home = IpAddr::V4(127,0,0,1);
  8. let loopback = IpAddr::V6(String::from("::1"));
  9. println!("{:?},{:?}",home,loopback)
  10. }

为枚举定义方法

和struct一样,使用impl关键字,示例:

  1. #[derive(Debug)]
  2. enum Message {
  3. Quit,
  4. Move {x:i32,y:i32},
  5. Write(String),
  6. ChangeColor(i32,i32,i32),
  7. }
  8. impl Message {
  9. fn call(&self) {
  10. println!("{:?}",self)
  11. }
  12. }
  13. fn main(){
  14. let q = Message::Quit;
  15. let m = Message::Move{ x:12,y:24 };
  16. let w = Message::Write(String::from("hello"));
  17. let c = Message::ChangeColor(0,255,255);
  18. m.call()
  19. }

其余可参考前一篇struct相关文章中的定义方法的模块,基本一致

枚举与模式匹配

Option枚举

Option枚举直接定义在标准库的预导入(Prelude)模块中。
该枚举用于描述: 某个值可能存在(可能是某种类型)或可能不存在。(额…好像有点绕)

那为什么会存在一个这么绕的枚举?
那是因为 Rust没有Null ,在很多其它语言中,都存在Null,它是一个表示”没有值“的值。但是Null的创作者曾经说过,Null引用是一个:billion-dollar mistake。所以Rust摒弃了Null类型,但是尽管Null存在很多问题,但是Null的概念还是很有用的,语言还是需要描述:因为某种原因而变为无效或缺失的值。所以Rust提供了一个类似Null概念的枚举,它就是 Option(泛型,前端玩家直接参考TS)。

在标准库中的定义

  1. enum Option<T>{
  2. Some(T),
  3. None,
  4. }

因为它是在预导入模块中的,所以在使用时是不需要 Option::Some(T) 或者 Option::None 的形式去写的,直接使用 Some<T>None 即可,示例:

  1. fn main(){
  2. let some_number = Some(5);
  3. let some_string = Some("String");
  4. let absent_number: Option<i32> = None;
  5. }

image.png
image.png

从图中可以看出,第一行定义Some(5),Rust的编译器是可以推断出当前的 T 是i32类型,但是最后一行,如果不手动指定 Option<i32> 类型,仅仅赋值None这个变体,编译器就会无法推断当前值的类型,就会编译报错,所以定义None时,必须手动指定类型。

Option 和 Null 相比的优势

  • 在Rust中 Option<T>T 是两种不同的类型,不可以把Option<T>直接当成T来使用
  • 若想使用 Option<T> 中的 T 则必须将它转换为 T ,这就避免了None值泛滥的情况

错误示例:

  1. fn main(){
  2. let x:i8 = 5;
  3. let y:Option<i8> = Some(5);
  4. let some = x + y; // 这样写就会报错:cannot add `std::option::Option<i8>` to `i8`
  5. }

正确用例:

  1. fn main(){
  2. let x:i8 = 5;
  3. let y:Option<i8> = Some(5);
  4. let some = x + y.unwrap(); // 转换为i8类型
  5. println!("{}",some)
  6. }

前端瞎叨叨: 在js其实也有类似的问题,尽管因为隐式类型转换基本减少了运算时出现的问题,但是当我们在使用类似_a.b_这样的语法时,也会存在VM370:1 Uncaught TypeError: Cannot read properties of null (reading ‘b’) 这种错误。从TS借鉴到标准里的可选链就是为了解决这类问题的,但是这毕竟还是需要从”人“的维度来避免。
Rust从前面的基础学习中就不难发现,他就是从”规范”的维度来避免的,如果你的写法是可能会出现错误的,那就在编译阶段卡住,借助这种方式来实现语言的安全。

控制流运算符 - match

match允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码。模式可以是字面值、变量名、通配符等等。示例:

  1. enum Coin {
  2. Penny,
  3. Nickel,
  4. Dime,
  5. Quarter,
  6. }
  7. fn value_in_coins(coin:Coin)->u8{
  8. match coin{
  9. Coin::Penny => {
  10. println!("penny");
  11. 1
  12. },
  13. Coin::Nickel => 5,
  14. Coin::Dime => 10,
  15. Coin::Quarter => 25,
  16. }
  17. }
  18. fn main(){
  19. println!("{:?}",value_in_coins(Coin::Penny))
  20. }

匹配的分支可以绑定到被配置对象的部分值,借此我们可以从enum变体中取值,示例:

  1. #[derive(Debug)]
  2. enum UsState {
  3. Alabama,
  4. Alaska,
  5. }
  6. enum Coin {
  7. Penny,
  8. Nickel,
  9. Dime,
  10. Quarter(UsState),
  11. }
  12. fn value_in_coins(coin:Coin)->u8{
  13. match coin{
  14. Coin::Penny => 1,
  15. Coin::Nickel => 5,
  16. Coin::Dime => 10,
  17. Coin::Quarter(state) => {
  18. print!("{:?}",state);
  19. 25
  20. },
  21. }
  22. }
  23. fn main(){
  24. println!("{:?}",value_in_coins(Coin::Quarter(UsState::Alabama)))
  25. }

既然我们知道match的使用,且匹配分支可以从enum变体中取值,那我们回到Option<T>类型的使用上:通过match匹配Option<T>,示例:

  1. fn main(){
  2. let num = Some(3);
  3. let num_plus_five = plus_five(num);
  4. println!("{:?}",num_plus_five)
  5. }
  6. fn plus_five(x:Option<i32>)->Option<i32>{
  7. match x{
  8. None=>None,
  9. Some(num)=>Some(num+5)
  10. }
  11. }

match匹配必须穷举所有的可能性,如果表达式可能性太多,可以使用:_通配符来代替没有写出来的值,示例:

  1. fn main(){
  2. let v:u8 = 0;
  3. match v {
  4. 1 => print!("1"),
  5. 3 => print!("3"),
  6. 5 => print!("5"),
  7. _ => print!("other"), // 要放在最后面,且如果不加则会报错
  8. }
  9. }