35、携带数据的枚举变体
枚举变体可以携带数据
enum Status {
ToDo,
InProgress,
Done,
}
我们的枚举通常称为C风格的枚举(C-Style enum
)。
每个变体都是一个简单的标签,有点像命名常量。很多其他的语言也有这种枚举方式:比如C/C++,Java,C#,Python等等。
Rust可牛逼了,它可不一样,它能把数据附加到每个变体,你就说牛逼不牛逼,人家都干不了,他能干得了。
Variants(变体)
比如说我们想要存储当前正在处理Ticket
的工作人员的姓名。
只有当Ticket的status
为InProgress
的时候,我们才能获得这个信息。他不会被用在一个to-do
Ticket或一个Done
Ticket身上。我们可以通过为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 */ }
}
乍一看感觉不是很复杂对吧,如果这个Ticket
的Status
是InProgress
的话,就返回携带的字符串,如果不是,那么就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。
拜拜了,这一节到这了。