35、携带数据的枚举变体

枚举变体可以携带数据

 

enum Status {
    ToDo,
    InProgress,
    Done,
}

我们的枚举通常称为C风格的枚举(C-Style enum)。

每个变体都是一个简单的标签,有点像命名常量。很多其他的语言也有这种枚举方式:比如C/C++,Java,C#,Python等等。

 

Rust可牛逼了,它可不一样,它能把数据附加到每个变体,你就说牛逼不牛逼,人家都干不了,他能干得了。

 

Variants(变体)

 

比如说我们想要存储当前正在处理Ticket的工作人员的姓名。

只有当Ticket的statusInProgress的时候,我们才能获得这个信息。他不会被用在一个to-doTicket或一个DoneTicket身上。我们可以通过为InProgress变体附加一个String字段来对这种情况进行建模:

enum Status {
    ToDo,
    InProgress {
        assigned_to: String,
    },
    Done,
}

 

InProgress 现在是一个类似结构体的变体。

事实上,该语法反应了我们用来定义结构体的语法——它只是作为变体内联(inlined)在了枚举中。

 

访问变体数据

 

如果我们尝试访问Status实例上的assigned_to

let status: Status = /* */;

// This won't compile
// 这将不会编译
println!("Assigned to: {}", status.assigned_to);

 

编译器会阻止我们:

error[E0609]: no field `assigned_to` on type `Status`
 --> src/main.rs:5:40
  |
5 |     println!("Assigned to: {}", status.assigned_to);
  |                                        ^^^^^^^^^^^ unknown field

 

assigned_to是特定于变体的(variant-specific),它在所有的Status实例上都不可用。

 

要访问assigned_to,我们需要使用模式匹配(pattern matching):

match status {
    Status::InProgress { assigned_to } => {
        println!("Assigned to: {}", assigned_to);
    },
    Status::ToDo | Status::Done => {
        println!("ToDo or Done");
    }
}

 

Bindings(绑定)

 

在模式匹配Status::InProgress { assigned_to }中,assigned_to就是一个绑定。

 

我们可以解构Status::InProgress变体,并将assigned_to绑定到一个新的变量上面,也命名为assigned_to

我们也可以把字段绑定到不同的变量名:

match status {
    Status::InProgress { assigned_to: person } => {
        println!("Assigned to: {}", person);
    },
    Status::ToDo | Status::Done => {
        println!("ToDo or Done");
    }
}

 

练习

不行,受不了了,这个RustRover题目的作者的脑子是有问题吗?合着这题都是睡着觉出的?梦到啥出啥?

每次做都感觉不像是正常人类能做出来的,来,看一下这一节的题目:

// TODO: Implement `Ticket::assigned_to`.
//  Return the name of the person assigned to the ticket, if the ticket is in progress.
//  Panic otherwise.

#[derive(Debug, PartialEq)]
pub struct Ticket {
    title: String,
    description: String,
    status: Status,
}

#[derive(Debug, PartialEq)]
pub enum Status {
    ToDo,
    InProgress { assigned_to: String },
    Done,
}

impl Ticket {
    pub fn new(title: String, description: String, status: Status) -> Ticket {
        if title.is_empty() {
            panic!("Title cannot be empty");
        }
        if title.len() > 50 {
            panic!("Title cannot be longer than 50 bytes");
        }
        if description.is_empty() {
            panic!("Description cannot be empty");
        }
        if description.len() > 500 {
            panic!("Description cannot be longer than 500 bytes");
        }

        Ticket {
            title,
            description,
            status,
        }
    }
    pub fn assigned_to(&self) -> &str {
        m/* TODO */    }
}

乍一看感觉不是很复杂对吧,如果这个TicketStatusInProgress的话,就返回携带的字符串,如果不是,那么就panic。

好,很好的题目,很清晰的逻辑啊,这答案一下子不就有了吗,作者还可以。

 

于是,我编写了这样的代码:

pub fn assigned_to(&self) -> &str {
        match &self.status {
            Status::InProgress { assigned_to } => assigned_to,
            _ => panic!(),
        }
    }

整个题目只编码这一块就足够了,其实。

 

但是!他说我代码有问题!

我寻思,可能又是我哪里的细节没注意吧,我认真看了好几遍,找不出什么问题啊?还有前面的理论知识,我也看了一遍,实在找不出来……

 

于是我看了一下答案:

pub fn assigned_to(&self) -> &str {
        match &self.status {
            Status::InProgress { assigned_to } => assigned_to,
            _ => panic!("Only `In-Progress` tickets can be assigned to someone"),
        }
    }

 

?????你在逗我玩吗?

作者,你好好看看你自己出的题,你想让我们去猜你的想法吗?我们怎么知道panic里面要输出哪些字符串的?

一个字,

 

不行,这么好的史,到时候给Rust新手都尝一遍,:D。

 

拜拜了,这一节到这了。

阅读剩余
THE END