原文来自 fasterthanli.me/articles/a-…
为了提高使用某种编程语言的熟练度,人们必须大量阅读相关代码。
但如果你不明白其含义,又怎么能大量阅读呢?
在本文中,我不会只关注一两个概念,而是会尽可能多地浏览Rust代码片段,并解释其中包含的关键字和符号的含义。
准备好了吗?开始!
变量绑定
"let"关键字
“let”用于引入变量绑定:
let x; // declare "x"
x = 42; // assign 42 to "x"
这也可以写成一行:
let x = 42;
类型注解
你可以使用 : 显式指定变量的类型,这就是类型注解:
let x: i32; // `i32` is a signed 32-bit integer
x = 42;
// there's i8, i16, i32, i64, i128
// also u8, u16, u32, u64, u128 for unsigned
这也可以写成一行:
let x: i32 = 42;
未初始化的变量
如果你声明了一个名称并在之后对其进行初始化,编译器会阻止你在初始化之前使用它。
let x;
foobar(x); // error: borrow of possibly-uninitialized variable: `x`
x = 42;
然而,这样做完全没问题:
let x;
x = 42;
foobar(x); // the type of `x` will be inferred from here
丢弃值
下划线 _ 是一个特殊名称,或者更确切地说,是一个 “无名”。它基本上意味着丢弃某些东西:
// this does *nothing* because 42 is a constant
let _ = 42;
// this calls `get_thing` but throws away its result
let _ = get_thing();
以下划线开头的名称是常规名称,只是编译器不会警告它们未被使用:
// we may use `_x` eventually, but our code is a work-in-progress
// and we just wanted to get rid of a compiler warning for now.
let _x = 42;
变量遮蔽绑定
可以引入名称相同的不同绑定——你可以遮蔽变量绑定:
let x = 13;
let x = x + 3;
// using `x` after that line only refers to the second `x`,
//
// although the first `x` still exists(it'll be dropped
// when going out of scope), you can no longer refer to it.
元组
Rust有元组,你可以将其视为 “不同类型值的固定长度集合”。
let pair =('a', 17);
pair.0; // this is 'a'
pair.1; // this is 17
如果我们真的想标注pair的类型,我们会这样写:
let pair:(char, i32) = ('a', 17);
解构元组
元组在赋值时可以解构,这意味着它们会被分解为各自的字段:
let(some_char, some_int) = ('a', 17);
// now, `some_char` is 'a', and `some_int` is 17
当函数返回一个元组时,这尤其有用:
let (left, right) = slice.split_at(middle);
当然,在解构元组时,_ 可用于丢弃其中的一部分:
let (_, right) = slice.split_at(middle);
语句
分号表示语句的结束:
let x = 3;
let y = 5;
let z = y + x;
这意味着语句可以跨越多行:
let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
.iter()
.map(|x| x + 3)
.fold(0, |x, y| x + y);
(我们稍后会详细讲解这些实际上是什么意思。)
函数
fn 声明一个函数。
这里有一个无返回值函数:
fn greet() {
println!("Hi there!");
}
这里有一个返回32位有符号整数的函数。箭头表示其返回类型:
fn fair_dice_roll() -> i32 {
4
}
代码块
一对花括号声明一个块,块有自己的作用域:
// This prints "in", then "out"
fn main() {
let x = "out";
{
// this is a different `x`
let x = "in";
println!("{}", x);
}
println!("{}", x);
}
块是表达式
代码块也是表达式,这意味着它们会求值为一个值。
// this:
let x = 42;
// is equivalent to this:
let x = { 42 };
在一个代码块中,可以有多个语句:
let x = {
let y = 1; // first statement
let z = 2; // second statement
y + z // this is the *tail* - what the whole block will evaluate to
};
隐式返回
这就是为什么“省略函数末尾的分号”等同于返回,即以下两者是等效的:
fn fair_dice_roll() -> i32 {
return 4;
}
fn fair_dice_roll() -> i32 {
4
}
一切皆为表达式
if条件语句也是表达式:
fn fair_dice_roll() -> i32 {
if feeling_lucky {
6
} else {
4
}
}
一个match也是一个表达式:
fn fair_dice_roll() -> i32 {
match feeling_lucky {
true => 6,
false => 4,
}
}
字段访问和方法调用
点通常用于访问值的字段:
let a = (10, 20);
a.0; // this is 10
let amos = get_some_struct();
amos.nickname; // this is "fasterthanlime"
或者对一个值调用方法:
let nick = "fasterthanlime";
nick.len(); // this is 14
模块,"use"语法
双冒号 :: 类似,但它作用于命名空间。
在这个例子中,std 是一个 *crate*(类似于库),cmp 是一个 *模块*(类似于源文件),min 是一个 *函数*:
let least = std:: cmp:: min(3, 8); // this is 3
use 指令可用于将来自其他命名空间的名称 “引入作用域”:
use std::cmp::min;
let least = min(7, 1); // this is 1
在use指令中,花括号有另一种含义:它们是 “通配符”。如果我们想同时导入min和max,可以使用以下任何一种方式:
// this works:
use std::cmp::min;
use std::cmp::max;
// this also works:
use std::cmp::{min, max};
// this also works!
use std::{cmp::min, cmp::max};
通配符(*)允许你从命名空间中导入所有符号:
// this brings `min` and `max` in scope, and many other things
use std::cmp::*;
类型也是命名空间
类型也是命名空间,并且方法可以像普通函数一样被调用:
let x = "amos".len(); // this is 4
let x = str::len("amos"); // this is also 4
标准库前置声明
str 是一种基本类型,但许多非基本类型默认也在作用域内。
// `Vec` is a regular struct, not a primitive type
let v = Vec::new();
// this is exactly the same code, but with the *full* path to `Vec`
let v = std::vec::Vec::new();
这之所以可行,是因为Rust会在每个模块的开头插入以下内容:
use std::prelude::v1::*;
(它反过来又重新导出了许多符号,比如Vec、String、Option和Result。)
结构体
结构体使用 struct 关键字声明:
struct Vec2 {
x: f64, // 64-bit floating point, aka "double precision"
y: f64,
}
它们可以使用 结构体字面量 进行初始化:
let v1 = Vec2 { x: 1.0, y: 3.0 };
let v2 = Vec2 { y: 2.0, x: 4.0 };
// the order does not matter, only the names do
结构体更新语法
有一种从另一个结构体初始化其余字段的快捷方式:
let v3 = Vec2 {
x: 14.0,
..v2
};
这被称为“结构体更新语法”,只能出现在最后一个位置,且后面不能跟逗号。
请注意,其余字段可能意味着 所有字段:
let v4 = Vec2 { ..v3 };
解构结构体
结构体和元组一样,可以进行解构。
就像这是一个有效的 let 模式:
let (left, right) = slice.split_at(middle);
同样,下面这个也是:
let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
// `x` is now 3.0, `y` is now `6.0`
还有这个:
let Vec2 { x, .. } = v;
// this throws away `v.y`
模式与解构
使用if let进行解构
let 模式可以在 if 中用作条件:
struct Number {
odd: bool,
value: i32,
}
fn main() {
let one = Number { odd: true, value: 1 };
let two = Number { odd: false, value: 2 };
print_number(one);
print_number(two);
}
fn print_number(n: Number) {
if let Number { odd: true, value } = n {
println!("Odd number: {}", value);
} else if let Number { odd: false, value } = n {
println!("Even number: {}", value);
}
}
// this prints:
// Odd number: 1
// Even number: 2
匹配分支是模式
match 分支同样也是模式,就像 if let 一样:
fn print_number(n: Number) {
match n {
Number { odd: true, value } => println!("Odd number: {}", value),
Number { odd: false, value } => println!("Even number: {}", value),
}
}
// this prints the same as before
详尽匹配
一个match必须是详尽无遗的:至少有一个分支需要匹配。
fn print_number(n: Number) {
match n {
Number { value: 1, .. } => println!("One"),
Number { value: 2, .. } => println!("Two"),
Number { value, .. } => println!("{}", value),
// if that last arm didn't exist, we would get a compile-time error
}
}
如果这有困难,_ 可以用作一个 “通配” 模式:
fn print_number(n: Number) {
match n.value {
1 => println!("One"),
2 => println!("Two"),
_ => println!("{}", n.value),
}
}
方法
你可以在自己定义的类型上声明方法:
struct Number {
odd: bool,
value: i32,
}
impl Number {
fn is_strictly_positive(self) -> bool {
self.value > 0
}
}
并且像平常一样使用它们:
fn main() {
let minus_two = Number {
odd: false,
value: -2,
};
println!("positive? {}", minus_two.is_strictly_positive());
// this prints "positive? false"
}
不可变性
变量绑定默认是不可变的,这意味着其内部无法被改变:
fn main() {
let n = Number {
odd: true,
value: 17,
};
n.odd = false; // error: cannot assign to `n.odd`,
// as `n` is not declared to be mutable
}
并且它们不能被赋值给:
fn main() {
let n = Number {
odd: true,
value: 17,
};
n = Number {
odd: false,
value: 22,
}; // error: cannot assign twice to immutable variable `n`
}
mut 使变量绑定可变:
fn main() {
let mut n = Number {
odd: true,
value: 17,
}
n.value = 19; // all good
}
特征
特征是多种类型可以共有的东西:
trait Signed {
fn is_strictly_negative(self) -> bool;
}
孤儿规则
你可以实现:
- 在任何类型上实现你自己的某个特性
- 在你的某个类型上实现任何的特征
- 但不能在外部类型上实现外部特征
这些被称为“孤儿规则”。
以下是我们在自定义类型上对该特性的实现:
impl Signed for Number {
fn is_strictly_negative(self) -> bool {
self.value < 0
}
}
fn main() {
let n = Number { odd: false, value: -44 };
println!("{}", n.is_strictly_negative()); // prints "true"
}
我们在外部类型(甚至是原生类型)上的 trait:
impl Signed for i32 {
fn is_strictly_negative(self) -> bool {
self < 0
}
}
fn main() {
let n: i32 = -44;
println!("{}", n.is_strictly_negative()); // prints "true"
}
我们类型上的外部特征:
// the `Neg` trait is used to overload `-`, the
// unary minus operator.
impl std::ops::Neg for Number {
type Output = Number;
fn neg(self) -> Number {
Number {
value: -self.value,
odd: self.odd,
}
}
}
fn main() {
let n = Number { odd: true, value: 987 };
let m = -n; // this is only possible because we implemented `Neg`
println!("{}", m.value); // prints "-987"
}
Self 类型
一个impl块始终是针对某个类型的,因此,在该块内部,Self表示该类型:
impl std::ops::Neg for Number {
type Output = Self;
fn neg(self) -> Self {
Self {
value: -self.value,
odd: self.odd,
}
}
}
标记特征
有些特性是标记——它们并不表明某个类型实现了某些方法,而是表明可以对某个类型执行某些操作。
例如,i32 实现了 Copy 特性(简而言之,i32 是 Copy),所以以下代码可以正常运行:
fn main() {
let a: i32 = 15;
let b = a; // `a` is copied
let c = a; // `a` is copied again
}
这样也可行:
fn print_i32(x: i32) {
println!("x = {}", x);
}
fn main() {
let a: i32 = 15;
print_i32(a); // `a` is copied
print_i32(a); // `a` is copied again
}
但是Number结构体不是Copy类型,所以这样不行:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `n` is moved into `m`
let o = n; // error: use of moved value: `n`
}
这样也不行:
fn print_number(n: Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
let n = Number { odd: true, value: 51 };
print_number(n); // `n` is moved
print_number(n); // error: use of moved value: `n`
}
但如果 print_number 采用不可变引用,代码就能正常运行:
fn print_number(n: &Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
let n = Number { odd: true, value: 51 };
print_number(&n); // `n` is borrowed for the time of the call
print_number(&n); // `n` is borrowed again
}
如果一个函数接受一个 可变 引用,它也能正常工作 —— 但前提是我们的变量绑定也是 mut。
fn invert(n: &mut Number) {
n.value = -n.value;
}
fn print_number(n: &Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
// this time, `n` is mutable
let mut n = Number { odd: true, value: 51 };
print_number(&n);
invert(&mut n); // `n is borrowed mutably - everything is explicit
print_number(&n);
}
特征方法接收者
特征方法也可以通过引用或可变引用接收self:
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}
调用特征方法时,接收者会被隐式借用:
fn main() {
let n = Number { odd: true, value: 51 };
let mut m = n.clone();
m.value += 100;
print_number(&n);
print_number(&m);
}
为突出这一点:以下内容是等效的:
let m = n.clone();
let m = std::clone::Clone::clone(&n);
像Copy这样的标记特征没有方法:
// note: `Copy` requires that `Clone` is implemented too
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}
impl std::marker::Copy for Number {}
现在,Clone 仍然可以使用:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n.clone();
let o = n.clone();
}
但是 Number 值将不再被移动:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `m` is a copy of `n`
let o = n; // same. `n` is neither moved nor borrowed.
}
派生特征
有些特性非常常见,可以通过使用 derive 属性自动实现:
#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
// this expands to `impl Clone for Number` and `impl Copy for Number` blocks.
泛型
泛型函数
函数可以是泛型的:
fn foobar<T>(arg: T) {
// do something with `arg`
}
它们可以有多个类型参数,这些参数随后可用于函数的声明及其主体中,而不是具体的类型:
fn foobar<L, R>(left: L, right: R) {
// do something with `left` and `right`
}
类型参数约束(特征边界)
类型参数通常有约束,这样你就可以对它们实际做点什么。
最简单的约束只是特征名:
fn print<T: Display>(value: T) {
println!("value = {}", value);
}
fn print<T: Debug>(value: T) {
println!("value = {:?}", value);
}
类型参数约束有一种更长的语法:
fn print<T>(value: T)
where
T: Display,
{
println!("value = {}", value);
}
约束条件可能会更复杂:它们可能要求一个类型参数实现多个特征:
use std::fmt::Debug;
fn compare<T>(left: T, right: T)
where
T: Debug + PartialEq,
{
println!("{:?} {} {:?}", left, if left == right { "==" } else { "!=" }, right);
}
fn main() {
compare("tea", "coffee");
// prints: "tea" != "coffee"
}
单态化
泛型函数可以被视为命名空间,其中包含无数具有不同具体类型的函数。
与板条箱、模块和类型一样,泛型函数也可以使用 :: “探索”(导航?)。
fn main() {
use std::any::type_name;
println!("{}", type_name::<i32>()); // prints "i32"
println!("{}", type_name::<(f64, char)>()); // prints "(f64, char)"
}
这被亲切地称为 turbofish 语法,因为 ::<> 看起来像一条鱼。
泛型结构体
结构体也可以是泛型的:
struct Pair<T> {
a: T,
b: T,
}
fn print_type_name<T>(_val: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let p1 = Pair { a: 3, b: 9 };
let p2 = Pair { a: true, b: false };
print_type_name(&p1); // prints "Pair<i32>"
print_type_name(&p2); // prints "Pair<bool>"
}
示例:Vec<T>
标准库类型 Vec(类似于堆分配数组)是泛型的:
fn main() {
let mut v1 = Vec::new();
v1.push(1);
let mut v2 = Vec::new();
v2.push(false);
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
说到 Vec,它有一个宏,或多或少能提供 “向量字面量”:
fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![true, false, true];
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
宏
所有的 name!()、name![] 或 name!{} 都会调用宏。宏只是展开为常规代码。
实际上,println 是一个宏:
fn main() {
println!("{}", "Hello there!");
}
这会展开为与以下内容具有相同效果的内容:
fn main() {
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
}
panic! 宏
panic 也是一个宏。它会带着一条错误消息强行终止执行,如果启用了相关功能,还会带上错误所在的文件名/行号:
fn main() {
panic!("This panics");
}
// output: thread 'main' panicked at 'This panics', src/main.rs:3:5
导致程序panic的函数
有些方法也会引发恐慌。例如,Option 类型可以包含某些内容,也可以不包含任何内容。如果对其调用 .unwrap() 且它不包含任何内容,就会引发恐慌:
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // this is fine
let o2: Option<i32> = None;
o2.unwrap(); // this panics!
}
// output: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21
枚举(和类型)
Option 不是结构体,而是一个 enum,有两个变体。
enum Option<T> {
None,
Some(T),
}
impl<T> Option<T> {
fn unwrap(self) -> T {
// enums variants can be used in patterns:
match self {
Self::Some(t) => t,
Self::None => panic!(".unwrap() called on a None option"),
}
}
}
use self::Option::{None, Some};
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // this is fine
let o2: Option<i32> = None;
o2.unwrap(); // this panics!
}
// output: thread 'main' panicked at '.unwrap() called on a None option', src/main.rs:11:27
Result 也是一个枚举类型,它可以包含某些内容,也可以包含一个错误:
enum Result<T, E> {
Ok(T),
Err(E),
}
当解包且包含错误时,它也会引发恐慌。
生命周期
变量绑定有一个“生命周期”:
fn main() {
// `x` doesn't exist yet
{
let x = 42; // `x` starts existing
println!("x = {}", x);
// `x` stops existing
}
// `x` no longer exists
}
同样,引用也有生命周期:
fn main() {
// `x` doesn't exist yet
{
let x = 42; // `x` starts existing
let x_ref = &x; // `x_ref` starts existing - it borrows `x`
println!("x_ref = {}", x_ref);
// `x_ref` stops existing
// `x` stops existing
}
// `x` no longer exists
}
引用的生命周期不能超过它所借用的变量绑定的生命周期:
fn main() {
let x_ref = {
let x = 42;
&x
};
println!("x_ref = {}", x_ref);
// error: `x` does not live long enough
}
借用规则(一次或多次不可变借用 异或 一次可变借用)
一个变量绑定可以被不可变借用多次:
fn main() {
let x = 42;
let x_ref1 = &x;
let x_ref2 = &x;
let x_ref3 = &x;
println!("{} {} {}", x_ref1, x_ref2, x_ref3);
}
在借用期间,变量绑定不能被修改:
fn main() {
let mut x = 42;
let x_ref = &x;
x = 13;
println!("x_ref = {}", x_ref);
// error: cannot assign to `x` because it is borrowed
}
在不可变借用期间,变量不能被可变借用:
fn main() {
let mut x = 42;
let x_ref1 = &x;
let x_ref2 = &mut x;
// error: cannot borrow `x` as mutable because it is also borrowed as immutable
println!("x_ref1 = {}", x_ref1);
}
具有泛型生命周期的函数
函数参数中的引用也有生命周期:
fn print(x: &i32) {
// `x` is borrowed (from the outside) for the
// entire time this function is called.
}
带引用参数的函数可以用具有不同生命周期的借用进行调用,因此:
- 所有接受引用的函数都是泛型的
- 生命周期是泛型参数
生命周期的名称以单引号 ' 开头:
// elided (non-named) lifetimes:
fn print(x: &i32) {}
// named lifetimes:
fn print<'a>(x: &'a i32) {}
这允许返回其生命周期取决于参数生命周期的引用:
struct Number {
value: i32,
}
fn number_value<'a>(num: &'a Number) -> &'a i32 {
&num.value
}
fn main() {
let n = Number { value: 47 };
let v = number_value(&n);
// `v` borrows `n` (immutably), thus: `v` cannot outlive `n`.
// While `v` exists, `n` cannot be mutably borrowed, mutated, moved, etc.
}
生命周期省略
当只有一个输入生命周期时,它无需命名,并且所有内容都具有相同的生命周期,因此以下两个函数是等效的:
fn number_value<'a>(num: &'a Number) -> &'a i32 {
&num.value
}
fn number_value(num: &Number) -> &i32 {
&num.value
}
结构体的生命周期泛型
结构体也可以在生命周期上具有泛型,这使得它们能够持有引用:
struct NumRef<'a> {
x: &'a i32,
}
fn main() {
let x: i32 = 99;
let x_ref = NumRef { x: &x };
// `x_ref` cannot outlive `x`, etc.
}
同样的代码,但增加了一个函数:
struct NumRef<'a> {
x: &'a i32,
}
fn as_num_ref<'a>(x: &'a i32) -> NumRef<'a> {
NumRef { x: &x }
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` cannot outlive `x`, etc.
}
同样的代码,但省略了生命周期:
struct NumRef<'a> {
x: &'a i32,
}
fn as_num_ref(x: &i32) -> NumRef<'_> {
NumRef { x: &x }
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` cannot outlive `x`, etc.
}
实现对生命周期的泛型化
impl 块也可以在生命周期上具有泛型:
impl<'a> NumRef<'a> {
fn as_i32_ref(&'a self) -> &'a i32 {
self.x
}
}
fn main() {
let x: i32 = 99;
let x_num_ref = NumRef { x: &x };
let x_i32_ref = x_num_ref.as_i32_ref();
// neither ref can outlive `x`
}
但你也可以进行省略:
impl<'a> NumRef<'a> {
fn as_i32_ref(&self) -> &i32 {
self.x
}
}
如果你根本不需要这个名称,还可以进行更激进的省略:
impl NumRef<'_> {
fn as_i32_ref(&self) -> &i32 {
self.x
}
}
'static 生命周期
存在一种特殊的生命周期,名为 'static,它在整个程序的生命周期内都有效。
字符串字面值具有'static生命周期:
struct Person {
name: &'static str,
}
fn main() {
let p = Person {
name: "fasterthanlime",
};
}
但是对String的引用不是静态的:
struct Person {
name: &'static str,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: &name };
// error: `name` does not live long enough
}
在最后一个例子中,局部的name不是&'static str,而是一个String。它是动态分配的,并且会被释放。它的生命周期比整个程序的生命周期短(即使它恰好位于main函数中)。
要在Person中存储非'static字符串,它需要满足以下条件之一:
A) 在生命周期上使用泛型:
struct Person<'a> {
name: &'a str,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: &name };
// `p` cannot outlive `name`
}
或者
B) 取得字符串的所有权
struct Person {
name: String,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: name };
// `name` was moved into `p`, their lifetimes are no longer tied.
}
结构体字面量赋值简写
说到:在结构体字面值中,当一个字段被设置为同名的变量绑定:
let p = Person { name: name };
可以像这样缩短:
let p = Person { name };
像clippy这样的工具会建议进行这些更改,如果你允许,甚至会以编程方式应用修复。
自有类型与引用类型
在Rust中,对于许多类型,都有拥有所有权和非拥有所有权的变体:
- 字符串:
String是拥有所有权的,&str是一个引用。 - 路径:
PathBuf是拥有所有权的,&Path是一个引用。 - 集合:
Vec<T>是拥有所有权的,&[T]是一个引用。
切片
Rust有切片,切片是对多个相邻元素的引用。
你可以借用向量的一部分,例如:
fn main() {
let v = vec![1, 2, 3, 4, 5];
let v2 = &v[2..4];
println!("v2 = {:?}", v2);
}
// output:
// v2 = [3, 4]
运算符重载
上述内容并非神奇之事。索引运算符(foo[index])通过 Index 和 IndexMut 特征进行了重载。
.. 语法只是范围字面量。范围不过是标准库中定义的几个结构体。
它们可以是无界的,如果最右端的边界前面有 =,则该边界可以是包含性的。
fn main() {
// 0 or greater
println!("{:?}", (0..).contains(&100)); // true
// strictly less than 20
println!("{:?}", (..20).contains(&20)); // false
// 20 or less than 20
println!("{:?}", (..=20).contains(&20)); // true
// only 3, 4, 5
println!("{:?}", (3..6).contains(&4)); // true
}
借用规则和切片
借用规则适用于切片。
fn tail(s: &[u8]) -> &[u8] {
&s[1..]
}
fn main() {
let x = &[1, 2, 3, 4, 5];
let y = tail(x);
println!("y = {:?}", y);
}
这等同于:
fn tail<'a>(s: &'a [u8]) -> &'a [u8] {
&s[1..]
}
这是合法的:
fn main() {
let y = {
let x = &[1, 2, 3, 4, 5];
tail(x)
};
println!("y = {:?}", y);
}
……但这只是因为 [1, 2, 3, 4, 5] 是一个 'static 数组。
所以,这是非法的:
fn main() {
let y = {
let v = vec![1, 2, 3, 4, 5];
tail(&v)
// error: `v` does not live long enough
};
println!("y = {:?}", y);
}
……因为向量是在堆上分配的,并且它的生命周期并非“静态”。
字符串切片(&str)
&str 值实际上是切片。
fn file_ext(name: &str) -> Option<&str> {
// this does not create a new string - it returns
// a slice of the argument.
name.split(".").last()
}
fn main() {
let name = "Read me. Or don't.txt";
if let Some(ext) = file_ext(name) {
println!("file extension: {}", ext);
} else {
println!("no file extension");
}
}
…所以借用规则在这里同样适用:
fn main() {
let ext = {
let name = String::from("Read me. Or don't.txt");
file_ext(&name).unwrap_or("")
// error: `name` does not live long enough
};
println!("extension: {:?}", ext);
}
可能出错的函数(Result<T, E>)
可能会失败的函数通常返回一个 Result:
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]);
println!("{:?}", s);
// prints: Ok("🍉")
let s = std::str::from_utf8(&[195, 40]);
println!("{:?}", s);
// prints: Err(Utf8Error { valid_up_to: 0, error_len: Some(1) })
}
如果你想在失败时触发恐慌,可以使用 .unwrap():
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
println!("{:?}", s);
// prints: "🍉"
let s = std::str::from_utf8(&[195, 40]).unwrap();
// prints: thread 'main' panicked at 'called `Result::unwrap()`
// on an `Err` value: Utf8Error { valid_up_to: 0, error_len: Some(1) }',
// src/libcore/result.rs:1165:5
}
或者使用 .expect(),用于自定义消息:
fn main() {
let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8");
// prints: thread 'main' panicked at 'valid utf-8: Utf8Error
// { valid_up_to: 0, error_len: Some(1) }', src/libcore/result.rs:1165:5
}
或者,你可以match:
fn main() {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => panic!(e),
}
// prints 🍉
}
或者你可以使用if let:
fn main() {
if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) {
println!("{}", s);
}
// prints 🍉
}
或者你可以将错误向上冒泡:
fn main() -> Result<(), std::str::Utf8Error> {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => return Err(e),
}
Ok(())
}
或者你也可以使用 ? 以简洁的方式实现:
fn main() -> Result<(), std::str::Utf8Error> {
let s = std::str::from_utf8(&[240, 159, 141, 137])?;
println!("{}", s);
Ok(())
}
解引用
* 运算符可用于 解引用,但访问字段或调用方法时无需这么做:
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
println!("({}, {})", p_ref.x, p_ref.y);
}
// prints `(1, 3)`
并且只有当类型为Copy时,你才能这样做:
struct Point {
x: f64,
y: f64,
}
fn negate(p: Point) -> Point {
Point {
x: -p.x,
y: -p.y,
}
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
negate(*p_ref);
// error: cannot move out of `*p_ref` which is behind a shared reference
}
// now `Point` is `Copy`
#[derive(Clone, Copy)]
struct Point {
x: f64,
y: f64,
}
fn negate(p: Point) -> Point {
Point {
x: -p.x,
y: -p.y,
}
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
negate(*p_ref); // ...and now this works
}
函数类型、闭包
闭包只是具有某些捕获上下文的 Fn、FnMut 或 FnOnce 类型的函数。
它们的参数是一对竖线(|)内由逗号分隔的名称列表。除非你想使用多条语句,否则它们不需要 花括号。
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
for_each_planet(|planet| println!("Hello, {}", planet));
}
// prints:
// Hello, Earth
// Hello, Mars
// Hello, Jupiter
借用规则同样适用于它们:
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// our closure borrows `greeting`, so it cannot outlive it
}
例如,这样行不通:
fn for_each_planet<F>(f: F)
where F: Fn(&'static str) + 'static // `F` must now have "'static" lifetime
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// error: closure may outlive the current function, but it borrows
// `greeting`, which is owned by the current function
}
但这样做可以:
fn main() {
let greeting = String::from("You're doing great");
for_each_planet(move |planet| println!("{}, {}", greeting, planet));
// `greeting` is no longer borrowed, it is *moved* into
// the closure.
}
FnMut与借用规则
要调用一个FnMut,需要对其进行可变借用,因此一次只能调用一次。
这是合法的:
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
foobar(|x| x * 2);
}
// output: 8
这是不合法的:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
println!("{}", f(f(2)));
// error: cannot borrow `f` as mutable more than once at a time
}
fn main() {
foobar(|x| x * 2);
}
这样再次合法:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
foobar(|x| x * 2);
}
// output: 8
FnMut 存在是因为有些闭包 可变借用 局部变量:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
x * acc
});
}
// output: 24
这些闭包不能传递给期望接收 Fn 的函数:
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
// error: cannot assign to `acc`, as it is a
// captured variable in a `Fn` closure.
// the compiler suggests "changing foobar
// to accept closures that implement `FnMut`"
x * acc
});
}
FnOnce闭包只能调用一次。它们存在的原因是,有些闭包会在捕获变量时将已被移动的变量移出:
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
}
fn main() {
let s = String::from("alright");
foobar(move || s);
// `s` was moved into our closure, and our
// closures moves it to the caller by returning
// it. Remember that `String` is not `Copy`.
}
这是自然强制的,因为 FnOnce 闭包需要被 移动 才能被调用。
所以,例如,这样是不合法的:
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
println!("{}", f());
// error: use of moved value: `f`
}
而且,如果你需要证据证明我们的闭包确实移动了 s,那么这也是非法的:
fn main() {
let s = String::from("alright");
foobar(move || s);
foobar(move || s);
// use of moved value: `s`
}
但这样没问题:
fn main() {
let s = String::from("alright");
foobar(|| s.clone());
foobar(|| s.clone());
}
这里有一个带两个参数的闭包:
fn foobar<F>(x: i32, y: i32, is_greater: F)
where F: Fn(i32, i32) -> bool
{
let (greater, smaller) = if is_greater(x, y) {
(x, y)
} else {
(y, x)
};
println!("{} is greater than {}", greater, smaller);
}
fn main() {
foobar(32, 64, |x, y| x > y);
}
这里有一个闭包,它忽略了两个参数:
fn main() {
foobar(32, 64, |_, _| panic!("Comparing is futile!"));
}
这里有一个略显令人担忧的关闭情况:
fn countdown<F>(count: usize, tick: F)
where F: Fn(usize)
{
for i in (1..=count).rev() {
tick(i);
}
}
fn main() {
countdown(3, |i| println!("tick {}...", i));
}
// output:
// tick 3...
// tick 2...
// tick 1...
厕所关闭
这是一则厕所关闭通知:
fn main() {
countdown(3, |_| ());
}
它之所以叫这个名字,是因为 |_|() 看起来像马桶。
循环,迭代器
任何可迭代的对象都可以在for in循环中使用。
我们刚刚看到了range的用法,但它也适用于Vec:
fn main() {
for i in vec![52, 49, 21] {
println!("I like the number {}", i);
}
}
或者一个切片:
fn main() {
for i in &[52, 49, 21] {
println!("I like the number {}", i);
}
}
// output:
// I like the number 52
// I like the number 49
// I like the number 21
或者一个实际的迭代器:
fn main() {
// note: `&str` also has a `.bytes()` iterator.
// Rust's `char` type is a "Unicode scalar value"
for c in "rust".chars() {
println!("Give me a {}", c);
}
}
// output:
// Give me a r
// Give me a u
// Give me a s
// Give me a t
即使迭代器中的元素经过了过滤、映射和扁平化处理:
fn main() {
for c in "SuRPRISE INbOUND"
.chars()
.filter(|c| c.is_lowercase())
.flat_map(|c| c.to_uppercase())
{
print!("{}", c);
}
println!();
}
// output: UB
返回闭包
你可以从函数中返回一个闭包:
fn make_tester(answer: String) -> impl Fn(&str) -> bool {
move |challenge| {
challenge == answer
}
}
fn main() {
// you can use `.into()` to perform conversions
// between various types, here `&'static str` and `String`
let test = make_tester("hunter2".into());
println!("{}", test("******"));
println!("{}", test("hunter2"));
}
捕获到闭包中
你甚至可以将对函数某些参数的引用,移入它返回的闭包中:
fn make_tester<'a>(answer: &'a str) -> impl Fn(&str) -> bool + 'a {
move |challenge| {
challenge == answer
}
}
fn main() {
let test = make_tester("hunter2");
println!("{}", test("*******"));
println!("{}", test("hunter2"));
}
// output:
// false
// true
或者,省略生命周期:
fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ {
move |challenge| {
challenge == answer
}
}
结论
至此,我们达到了预计的30分钟阅读时间节点,此时你应该能够读懂在网上找到的大部分Rust代码。
编写Rust代码与阅读Rust代码的体验截然不同。一方面,你不是在阅读某个问题的解决方案,而是在实际解决问题。另一方面,Rust编译器会提供很大帮助。
Rust编译器针对本文中提到的所有错误都提供了高质量的诊断信息(其中包括建议)。
而且,当缺少某个提示时,编译器团队勇于添加该提示。
如需更多Rust相关资料,你可能需要查看:
玩得开心!