前言
泛型在 Rust 中非常重要。他们可用于表示可空值(即可能还有值的变量)、错误处理、集合等等。下面展示经常使用的基础泛型知识。
泛型是什么
泛型允许我们不完全定义一个 struct 或者 enum ,使编译器能够根据我们的代码使用情况,在编译时创建一个完全定义的版本。
Rust 通常可以通过查看我们的实例化来推断最终的类型,但是如果需要帮助,可以使用 :: 操作符来显式地操作,该操作符也被称为 turbofish
// 一个部分定义的结构体
struct BagOgHolding<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 用于表示没有值。他给编程语言带来了困难,因为它使我们的程序在与变量字段交互时可能会失败。
Rust没有 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 可以在任何地方创建其实例。
// 一个部分定义的结构体
struct BagOfHolding<T> {
// 我们的参数类型T可以传递给其他
item: Option<T>,
}
fn main() {
// 注意: 放一个 i32 的 bag ,里面什么都没有!
// 我们必须注明类型, 否则 Rust 不知道 bag 的类型
let i32_bag = BagOfHolding::<i32> { item: None };
if i32_bag.item.is_none() {
println!("there's nothing in the bag!");
} else {
println!("there's something in the bag1");
}
let i32_bag = BagOfHolding::<i32> { item: Some(42) };
if i32_bag.item.is_some() {
println!("there's something in the bag!");
} else {
println!("there's nothing in the bag!");
}
// match 可以让我们优雅的解构 Option ,并且确保我们处理了所有的可能情况!
match i32_bag.item {
Some(v) => println!("found {} in the bag!", v),
None => println!("found nothing"),
}
}
Result
Rust 有一个内置的泛型叫做 Result ,它可以让我们返回一个可能包含错误的值。这是编程语言进行错误处理的惯用方法。
enum Result<T, E> {
Ok(T),
Err(F),
}
注意我们的泛型有多个用逗号分隔的参数化的类型。
这个枚举很常见,使用关键词 Ok 和 Err 可以在任何地方创建其实例。
fn do_something_that_might_fail(i:i32) -> Result<f32, String> {
if i == 42 {
Ok(42.0)
} else {
Err(String::from("this is not the right number"))
}
}
fn main() {
let result = do_something_that_might_fail(12);
// match 让我们可以优雅的解构 Rust,并且确保我们处理了所有的情况
match result {
Ok(v) => println!("found {}", v),
Err(e) => println!("Error: {}", e),
}
}
可失败的主函数
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() {
let result = do_something_that_might_fail(12);
match result {
Ok(v) => println!("found {}", v),
Err(e) => {
// 如何优雅的处理错误
// 返回一个说明发生了什么的新错误!
return Err(String::from("something were wrong in main!"));
},
}
Ok(())
}
优雅地错误处理
Result 如此常见以至于Rust有个强大的操作符 ? 来与之匹配。
以下两个表达式是等价的:
do_something_that_might_fail()?
match do_something_that_might_fail() {
Ok(v) => v,
Err(e) => return Err(e),
}
实例:
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() {
// 可以比较下节省了多少代码
let v = do_something_that_might_fail(42)?;
println!("found {}", v);
Ok(())
}
丑陋的 Option/Result 处理
当我们只是想试图快速的写一些代码时, Option/Result 对付起来都是有点无聊。这时候 Option/Result 都有一个名为 unwrap 的函数:这个函数可以简单粗暴地获取其中的值。
unwrap 会:
- 获取 Option/Result 的值
- 如果枚举类型是 None/Err,则会 panic!
这两段代码是等价的:
my_option.unwrap()
match my_option() {
Some(v) => v,
None => panic!("something error message generated"),
}
类似的:
my_result.unwrap()
match my_result() {
Ok(v) => v,
Err(e) => panic!("something error message generated: {}", e),
}
不过做个好 Rustacean(发音rus da’ tions),正确的使用 match !
实例:
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() {
// 简洁但是假设性很强,而且很快就会变得丑陋
let v = do_something_that_might_fail(42).unwrap();
println!("found {}", v);
// 这会 panic !
let e = do_something_that_might_fail(1).unwrap();
println!("found {}", e);
Ok(())
}
Vectors
一些经常使用的泛型是集合类型。一个 vector 是可变长度的元素集合,以 Vec 结构表示。
比起手动构建,宏 Vec! 让我们可以轻松船舰 vector
Vec 有一个形如 iter() 的方法可以为一个 vector 创建迭代器,这允许我们轻松地将 vector 用到 for 循环中去。
内存细节:
- Vec 是一个结构体, 但是内部其实都保存了在堆上固定长度数据的引用。
- 一个 vector 开始有默认大小容量,当更多元素被添加进来后,它会重新在堆上分配一个新的并具有更大容量的定长列表。(类似C++的vector)
总结
这一章中,我们了解泛型带来的强大功能。下一章了解另外一个重要概念:数据所有权。