为什么 Rust 中的枚举(enum)很常见很好用? ? ?
将 Rust 作为第二言语学习的人的一个常见的反应往往是,比较于其他任何言语, 枚举(enums) 在Rust中 得到了更好的支撑 。粗略地浏览一下 Google 查找“Rust 中的枚举”会在“人们也查找过”中回来一个成果,询问“为什么 Rust 中的枚举如此好用”。乍一看,这好像是一个好问题;孤登时讲,枚举是代表 有多个值的容器类型 – 例如:方向(东,南,西,北)或季节(春夏秋冬)。可是,Rust 以此为基础并以其他言语中底子不存在的办法增强枚举。
在本文中,咱们将评论 Rust 枚举显着优于其他言语的原因,以及它们的一些用例。
快速回顾enum
首先,为不熟悉的人快速回顾一下 Rust 枚举的实践含义:枚举是能够表明界说数量的变体的类型。考虑以下枚举:
enum Directions {
Up,
Down,
Left,
Right
}
这代表了一些方向。与运用字符串比较,运用枚举的长处是,当咱们进行形式匹配时,咱们能够简略地匹配不同的变体,而不用考虑字符串的改变。
其他言语中的 enum
关于某些上下文,让咱们看一下枚举在其他言语中的姿态。在 TypeScript 中,在 Google 上粗略查找 Typescript 枚举将回来许多成果,这些成果要么告知您以下内容:
- 不要在 TypeScript 中运用枚举,由于它们很糟糕
- 运用枚举只需一种正确的办法
- 有许多运用枚举的过错办法,这些办法并不是立即显而易见的,由于枚举在编译为 JavaScript 时并不是一个东西
这告知咱们,虽然它们是 TypeScript 中的一项功用,但它们好像并不是很受欢迎 – 通常是由于用户过错或言语怪癖导致运用枚举变得尴尬。
在Java和其他言语中,应该注意的是,枚举显着更加合理,由于它们没有编译到 底层言语 来支撑——因而,必须在类中运用它们或运用办法重写之类的东西来做任何事情(就扩展或完成它们的功用而言)的本质意味着,作为一个全体,枚举并没有真正得到一流的支撑。其他言语,比如Go,纷歧定有枚举,可是你能够用这样的办法来表明枚举(在Go中):
const (
A base = iota
C
T
G
)
可是,短少官方的 enum 关键字意味着运用起来好像有些令人沮丧。
在 Rust 中,枚举经过作为 相似结构类型获得一流的支撑 – 因而您能够具有一个包括相似结构的结构的枚举,其间枚举变体 中存在 命名值,或者您能够具有一个元组结构按数字引证变量,或者您能够只运用枚举变量自身。尽管除非您实例化它,不然您无法(默认情况下)在没有额外的crate的情况下声明初始值,但经过完成与枚举变体匹配的办法,然后回来您想要的任何内容,将枚举变体转换为另一种类型相对简略。只需你想这样做。
借助 Result
和 Option
类型,枚举在 Rust 类型系统中也得到了相当多的运用,这两种类型构成了 Rust 中过错处理系统的基础。您还能够经过完成枚举的traits来增强枚举,咱们将在下面看到更多内容。
为 Enum 完成办法
Rust 中的枚举能够专门为枚举完成办法,无需类。咱们来看看下面的办法:
enum Number {
Odd(i64),
Even(i64)
}
该枚举代表一个数字以及它是奇数还是偶数。咱们可认为其完成一个办法,根据数字是否能够除以 2 主动实例化枚举变量,如下所示:
impl Number {
fn from_i64(num: i64) => Self {
match num % 2 == 0 {
true => Number::Even(num),
false => Number::Odd(num)
}
}
}
这消除了许多样板代码,并使运用 Number::from_i64(number)
更简略运用该办法。在其他言语中,您当然能够编写一个回来枚举的独自办法,可是能够在枚举自身下对其进行命名空间使代码更加简练。
就像 结构体(structs)一样,您也能够在枚举上运用派生宏;派生宏是 Rust 生态系统的重要组成部分,经过在编译时主动生成代码来简化样板代码生成。
Enum 作为过错类型
检查以下枚举:
#[derive(Debug)]
enum MyError {
SQLError(sqlx::Error),
RedisError(redis::RedisError),
Forbidden,
BadRequest,
Unauthorized
}
此枚举表明 Web 应用程序或许失利的几种不同办法:例如,SQL 查询或许会因语法不正确而导致过错,您的 Redis 服务器或许在连接到它时出现过错,而且用户也或许尝试访问他们想要的页面。不应访问或填写过错的表格。
Error trait 要求咱们的枚举类型同时完成 Debug
和 Display
– 咱们现已为 Debug 特征运用了派生宏,因而咱们不用手动完成它,但咱们确实这样做了需要完成 Display
。咱们能够经过匹配下面函数中的每个枚举变体来做到这一点:
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MyError::SQLError(e) => write!(f, format!("Something went wrong while using an SQL query: {e}")),
MyError::RedisError(e) => write!(f, format!("Something went wrong while using Redis: {e}")),
MyError::Forbidden => write!(f, "User tried to access a page but was forbidden!"),
MyError::BadRequest => write!(f, "User tried to submit a HTTP request but it returned 400!"),
MyError::Unauthorized => write!(f, "User tried to access a page but wasn't authorised!"),
}
}
}
完成此功用还能够免费为咱们供给 .to_string()
,而且在完成后将根据枚举变体回来上述内容 – 这对咱们很有用!
Error
trait类型如下所示:
pub trait Error: Debug + Display {
fn description(&self) -> &str { /* ... */ }
fn cause(&self) -> Option<&Error> { /* ... */ }
fn source(&self) -> Option<&(Error + 'static)> { /* ... */ }
}
可是,所有这些函数都是可选的,而且现已有默认完成 – 因而您能够简略地为您的类型完成 Error
,如下所示:
impl Error for MyError {}
从技术上讲,这将为您供给完成 – 当然,假如您想包括更多自界说行为(例如,包括特定枚举变体对保存变量的运用),您或许只想这样做。
当您运用像 Axum 或 Actix 这样的 Web 结构时,通常来说您不用自己完成 Error
– 您将完成结构运用的任何类型,同时也完成 Error
。例如,在 Axum 中, IntoResponse
特征完成了 Error
而且也是一个成功的回来类型,因而从技术上讲,您能够将 Result<impl IntoResponse, impl IntoResponse>
作为函数回来签名。让咱们看看您将怎么完成它。
impl IntoResponse for MyError {
fn into_response(&self) -> Response {
match self {
MyError::SQLError(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Error while using SQL: {e}")).into_response(),
MyError::RedisError(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Error while using Redis: {e}")).into_response(),
MyError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden!".to_string()).into_response(),
MyError::BadRequest => (StatusCode::FORBIDDEN, "Bad request. Did you fill something out wrong?".to_string()).into_response(),
MyError::Unauthorized => (StatusCode::FORBIDDEN, "Unauthorised!".to_string()).into_response(),
}
}
}
枚举作为过错类型十分有效:经过将过错类型设置为枚举,您只需要与枚举的每个分支进行匹配,而无需运用非翔实的形式符号( _
替换您不想匹配的枚举变体,然后为其回来一些内容。
Enum 作为new-type(“包装类型”)
咱们还能够将类型包装在枚举中,该枚举也或许有多个变体,其间包括来自单个crate或多个crate 的类型。与只是揭露另一位所述 crate 的 API 比较,这样做的好处是,您可认为自己的程序引入新功用,同时经过不需要与原始类型自身交互来坚持向后兼容性 – 您还能够运用它来创立笼统原始类型。例如, poise
crate 构建在 serenity
crate之上,经过将新类型揭露为笼统来供给更高档的函数,而不是运用低级函数。
另一个例子:运用咱们之前对 Display
trait的了解,咱们实践上能够在运用 .to_string()
时掩盖类型显示的内容!考虑一个包括暗码和该结构创立时刻的结构:
struct Password {
password: String,
created_at: DateTime<Utc>
}
咱们能够用一个枚举来掩盖它:
enum PasswordEnum {
Secured(Password),
Unsecured(Password)
}
现在咱们能够做两件事:
- 能够将暗码显示为一堆星星(基于长度)
- 能够回来暗码是否安全(根据某些规范)
See below for what this might look like:
请参阅下面的内容,了解它或许是什么姿态:
impl fmt::Display for PasswordEnum {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PasswordEnum::Secured(password) => {
password = password.chars().map(|_| "*".to_owned()).collect::<String>();
write!(f, password);
},
PasswordEnum::Unsecured(password) => {
password = password.chars().map(|_| "*".to_owned()).collect::<String>();
write!(f, password);
},
}
}
}
impl PasswordEnum {
fn is_secure(&self) -> bool {
match self {
PasswordEnum::Secured(_) => true,
PasswordEnum::Unsecured(_) => false
}
}
}
正如您所看到的,经过枚举运用new-type形式来发挥您的优势十分简略!您也能够运用结构来做到这一点。
结尾
感谢您的阅览,我期望您了解怎么在 Rust 中运用枚举!枚举十分强壮,是 Rust 开发的强壮支柱的一部分。
有爱好了解有关 Rust 的更多信息吗?这里有一些想法:
- 在 此处 了解有关宏的更多信息。
- 了解有关using design patterns in Rust. 。