Rust 语言学习之旅(2)
枚举
枚举允许你使用 enum 关键字创建一个新类型,该类型的值可以包含几个带标记的元素。
match 有助于确保对所有可能的枚举值进行彻底的处理,使其成为确保高质量代码的强大工具。
#![allow(dead_code)] // this line prevents compiler warnings enum Species { Crab, Octopus, Fish, Clam } struct SeaCreature { species: Species, name: String, arms: i32, legs: i32, weapon: String, } fn main() { let ferris = SeaCreature { species: Species::Crab, name: String::from("Ferris"), arms: 2, legs: 4, weapon: String::from("claw"), }; match ferris.species { Species::Crab => println!("{} is a crab",ferris.name), Species::Octopus => println!("{} is a octopus",ferris.name), Species::Fish => println!("{} is a fish",ferris.name), Species::Clam => println!("{} is a clam",ferris.name), } } |
带数据的枚举
enum 的元素可以有一个或多个数据类型,从而使其表现得像 C 语言中的联合。
当使用 match 对一个 enum 进行模式匹配时,可以将变量名称绑定到每个数据值。
enum 的内存细节:
- 枚举数据的内存大小等于它最大元素的大小。此举是为了让所有可能的值都能存入相同的内存空间。
- 除了元素数据类型(如果有)之外,每个元素还有一个数字值,用于表示它是哪个标签。
其他细节:
- Rust 的 enum 也被称为标签联合 (tagged-union)
- 把类型组合成一种新的类型,这就是人们所说的 Rust 具有 代数类型 的含义。
#![allow(dead_code)] // this line prevents compiler warnings enum Species { Crab, Octopus, Fish, Clam } enum PoisonType { Acidic, Painful, Lethal } enum Size { Big, Small } enum Weapon { Claw(i32, Size), Poison(PoisonType), None } struct SeaCreature { species: Species, name: String, arms: i32, legs: i32, weapon: Weapon, } fn main() { // SeaCreature's data is on stack let ferris = SeaCreature { // String struct is also on stack, // but holds a reference to data on heap species: Species::Crab, name: String::from("Ferris"), arms: 2, legs: 4, weapon: Weapon::Claw(2, Size::Small), }; match ferris.species { Species::Crab => { match ferris.weapon { Weapon::Claw(num_claws,size) => { let size_description = match size { Size::Big => "big", Size::Small => "small" }; println!("ferris is a crab with {} {} claws", num_claws, size_description) }, _ => println!("ferris is a crab with some other weapon") } }, _ => println!("ferris is some other animal"), } } |
泛型
泛型在 Rust 中非常重要。它们用于表示可空值(即可能还没有值的变量)、错误处理、集合等等! 在本章中,我们将学习你可能将会经常使用的基本泛型知识。
泛型允许我们不完全定义一个 struct 或 enum,使编译器能够根据我们的代码使用情况,在编译时创建一个完全定义的版本。
Rust 通常可以通过查看我们的实例化来推断出最终的类型,但是如果需要帮助,你可以使用 ::<T> 操作符来显式地进行操作, 该操作符也被称为 turbofish (它是我的好朋友!)。
// 一个部分定义的结构体类型 struct BagOfHolding<T> { item: T, } fn main() { // 注意:通过使用泛型,我们创建了编译时才创建的类型,使代码更大 // Turbofish 使之显式化 let i32_bag = BagOfHolding::<i32> { item: 42 }; let bool_bag = BagOfHolding::<bool> { item: true }; // Rust 也可以推断出泛型的类型! let float_bag = BagOfHolding { item: 3.14 }; // 注意:在现实生活中,不要把一袋东西放在另一袋东西里:) let bag_in_bag = BagOfHolding { item: BagOfHolding { item: "嘭!" }, }; println!( "{} {} {} {}", i32_bag.item, bool_bag.item, float_bag.item, bag_in_bag.item.item ); } |
空null
在其他语言中,关键字 null 用于表示没有值。它给编程语言带来了困难,因为它使我们的程序在与变量字段交互时可能失败。
Rust 没有 null,但这并不代表我们不知道表示空的重要性!我们可以使用一个我们已经了解过的工具来简单地表示这一点。
因为缺少 null 值,这种为一个或多个替代值提供 None 替代表示的模式非常常见, 泛型有助于解决这一难题。
enum Item { Inventory(String), // None represents the absence of an item None, } struct BagOfHolding { item: Item, } |
Option
Rust 有一个内置的泛型枚举叫做 Option,它可以让我们不使用 null 就可以表示可以为空的值。
enum Option<T> { None, Some(T), } |
这个枚举很常见,使用关键字 Some 和 None 可以在任何地方创建其实例。
Result
Rust 有一个内置的泛型枚举叫做 Result,它可以让我们返回一个可能包含错误的值。 这是编程语言进行错误处理的惯用方法。
enum Result<T, E> {
Ok(T),
Err(E),
}
注意我们的泛型有多个用逗号分隔的参数化的类型。
这个枚举很常见,使用关键字 Ok 和 Err 可以在任何地方创建其实例。
main 函数有可以返回 Result 的能力!
fn do_something_that_might_fail(i: i32) -> Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("this is not the right number")) } } // 主函数不返回值,但可能返回一个错误! fn main() -> Result<(), String> { let result = do_something_that_might_fail(12); match result { Ok(v) => println!("found {}", v), Err(_e) => { // 优雅地处理错误 // 返回一个说明发生了什么的新错误! return Err(String::from("something went wrong in main!")); } } // 注意我们在 Result Ok 中使用了一个单位值 // 表示一切都很好 Ok(()) } |
优雅地错误处理
Result 如此常见以至于 Rust 有个强大的操作符 ? 来与之配合。 以下两个表达式是等价的:
do_something_that_might_fail()? match do_something_that_might_fail() { Ok(v) => v, Err(e) => return Err(e), } |
Vectors
一些经常使用的泛型是集合类型。一个 vector 是可变长度的元素集合,以 Vec 结构表示。
比起手动构建,宏 vec! 让我们可以轻松地创建 vector。
Vec 有一个形如 iter() 的方法可以为一个 vector 创建迭代器,这允许我们可以轻松地将 vector 用到 for 循环中去。
内存细节:
- Vec 是一个结构体,但是内部其实保存了在堆上固定长度数据的引用。
- 一个 vector 开始有默认大小容量,当更多的元素被添加进来后,它会重新在堆上分配一个新的并具有更大容量的定长列表。(类似 C++ 的 vector)
fn main() { // 我们可以显式确定类型 let mut i32_vec = Vec::<i32>::new(); // turbofish <3 i32_vec.push(1); i32_vec.push(2); i32_vec.push(3); // 但是看看 Rust 是多么聪明的自动检测类型啊 let mut float_vec = Vec::new(); float_vec.push(1.3); float_vec.push(2.3); float_vec.push(3.4); // 这是个漂亮的宏! let string_vec = vec![String::from("Hello"), String::from("World")]; for word in string_vec.iter() { println!("{}", word); } } |