和大多数编程语言一样,Rust使用 enum 关键字定义枚举类型,用来表示一些数量有限的类型,例如常见的使用一个枚举类型表示星期几:
enum Week {Sun, Mon, Tue, Wed, Thu, Fri, Sat}
与C++相比,enum语法上的不同主要体现在两点:
- 在传统的C/C++中,枚举类型在编译后会退化为int类型,因此枚举类实际可以直接和Int进行比较和相互转换;Rust中枚举类则是一个真正的独立类型(类似于C++11中的强类型枚举),无法直接与其他类型进行运算;
- 默认情况下,Rust的枚举类型是无法直接进行比对运算的(即无法使用“==”运算符),常见的做法是使用match语句对枚举进行匹配
enum Week {Sun, Mon, Tue, Wed, Thu, Fri, Sat}
let today = Week::Mon;
//默认情况下,这种写法会编译报错
//if today == Week::Mon{
// println!("Today is Monday");
//}
match today {
Week::Mon =>{
println!("Today is Monday!");
}
_ => {
println!("Today is Not Monday");
}
};
Rust枚举最精妙的设计在于,Rust允许枚举绑定一个特定类型的数据成员;
来看一个业务中常见的场景:在不同组件之间,我们通常使用Message或者Command的形式在组件间通讯,并且往往需要在消息中传递一些额外的参数。例如,假设我们有一个组件负责显示相关的工作,我们给这个组件传递的消息内容可能有以下几类:
- 开、关显示,需要附加一个布尔值,表示开关状态;
- 调整显示亮度,需要附加一个数字,表示亮度值
- 调整分辨率,需要附加两个数据,表示分辨率
- 显示特定的内容,附加一个字符串表示需要显示的字符串
如果尝试为这个场景设计API,你会发现很难用一种简单而统一的方式去传递这些信息:我们需要一个参数表示消息类型,同时需要处理不同参数类型带来的不同参数列表。一种可能的方式是我们使用一个结构体来将类型和数据包装在一起,同时使用联合体存储数据,可能形如下列的形式:
struct Message{
MessageType type;
MessageDate data; // MessageDate 需要是一个Union类型,因为data的数据类型并不确定
}
而这个过程在Rust中会非常简单,只需要为每个消息类型附加一个数据成员即可:
enum Message {
Power(bool),
ChangeBrightness(i32),
ChangeSize(i32,i32),
Display(String)
}
在这个枚举类型中,我们定义了一系列不同的消息类型,同时为每个类型绑定一个特定类型的数据成员。在发送消息时,只需要生成对应的消息即可:
let bright = 80;
let msg = Message::ChangeBrightness(bright);
SendMessage(msg);
对于接收方,也只需要使用match匹配就可以轻松的从枚举中取出对应的数据成员:
match msg {
Message::ChangeBrightness(x) => {
println!("Target Brightness is {}", x);
//Do Something
}
//....
};
值得一提的是,上述代码中的x对于命名并无要求,仅仅是作为形式参数;如果你的业务代码对枚举附带的数据成员并不感兴趣,可以直接使用 “_” 通配符替代参数:
match msg {
//忽略了Message中的参数
Message::Power(_) => {
//Do Something
}
//忽略了Message中第二个参数,只接收第一个参数
Message::ChangeSize(x, _) => {
//Do Something
} //....
};
枚举高级用法:由数据成员到枚举类的映射
如果在声明一个带有数据成员的类型的枚举变量时,并不指定这个枚举具体数据成员的值,那么这个变量将会成为一个可调用函数(类似于闭包\Lambda),从它的数据成员类型映射到枚举类型;这种变量可以用于传参,并转化为对应的闭包函数类型;
enum Message {
I32Data(i32),
}
fn main() {
//msg 是一个Message枚举类型变量
let msg1 = Message::I32Data(50);
// f 是一个可调用对象,从i32映射到DemoMessage,相当于 Fn(i32)->DemoMessage
let f = Message::I32Data;
//以下的 msg 与 msg1 完全等价
let msg2 = f(50);
let msg3 = message_i32(50);
}
fn message_i32 (x : i32) -> Message
{
return Message:I32Data(x);
}
在上述的示例中,msg1是一个实际的枚举类型变量;注意接下来的 f 变量:它只定义了Message的类型,但是并没有指定具体的值;在这种情况下,f将会成为一个可调用对象,与下文中定义的 message_i32() 方法等价,可以通过传入参数返回对应的枚举类型变量。