微信链接 https://mp.weixin.qq.com/s/v8pICcujy87U3WiBPEy0DQ
原文链接 https://fasterthanli.me/articles/a-half-hour-to-learn-rust
在这篇文章中,作者并不关注于 1 个或几个关键概念,相反他希望通过代码块纵览 Rust 的各种特性,包括各种关键词与标识符的意义。多读代码,通过代码学习。(螃蟹哥建议:别只是读,动手敲代码更重要)

01 变量

先介绍 let 变量绑定:(注意,Rust 中一般称之为绑定)

  1. let x; // declare "x"
  2. x = 42; // assign 42 to "x"

你也可以写成一行:

  1. let x = 42;

通常,Rust 能够自动推断出变量的类型,但你可以使用 : 来显示指定变量的数据类型:(有时候必须显示指定)

  1. let x: i32; // `i32` is a signed 32-bit integer
  2. x =&nnbsp;42;
  3. // there's i8, i16, i32, i64, i128
  4. // also u8, u16, u32, u64, u128 for unsigned

同样的,你也可以写成一行(一般建议这么写):

  1. let x: i32 = 42;

如果你声明一个变量并在初始化之前就调用它,编译器会报错:

  1. let x;
  2. foobar(x); // error: borrow of possibly-uninitialized variable: `x`
  3. x = 42;

然而,这样做完全没问题:

  1. let x;
  2. x = 42;
  3. foobar(x); // the type of `x` will be inferred from here

下划线 _ 是一个特殊的变量名,或者更确切地说是「名称的缺失」。它的基本意思是扔掉,可以理解为垃圾桶:(和 Go 中的 _ 差不多)

  1. // this does *nothing* because 42 is a constant
  2. let _ = 42;
  3. // this calls `get_thing` but throws away its result
  4. let _ = get_thing();

以下划线开头的变量名是常规名称,只是编译器不会警告它们未被使用:

  1. // we may use `_x` eventually, but our code is a work-in-progress
  2. // and we just wanted to get rid of a compiler warning for now.
  3. let _x = 42;

可以引入具有相同名称的另一个绑定——这相当于隐藏前一个变量绑定:

  1. let x = 13;
  2. let x = x + 3;
  3. // using `x` after that line only refers to the second `x`,
  4. // the first `x` no longer exists.

Rust 有元组类型,你可以将其看作是“不同类型值的定长集合”。

  1. let pair = ('a', 17);
  2. pair.0; // this is 'a'
  3. pair.1; // this is 17

如果真的想显示指定 pair 的数据类型,可以这么写:

  1. let pair: (char, i32) = ('a', 17);

元组在赋值时可以被拆解,也就是它们可以被分解成各自的字段:

  1. let (some_char, some_int) = ('a', 17);
  2. // now, `some_char` is 'a', and `some_int` is 17

一个函数可以返还一个元组,这类似于多返回值:

  1. let (left, right) = slice.split_at(middle);

当然,在解构一个元组时,可以只分离它的一部分,这就用到了 _

  1. let (_, right) = slice.split_at(middle);

分号表示语句的结尾:

  1. let x = 3;
  2. let y = 5;
  3. let z = y + x;

不加分号意味着语句可以跨多行:(这些是什么意思,稍后解释)

  1. let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
  2. .iter()
  3. .map(|x| x + 3)
  4. .fold(0, |x, y| x + y);

02 函数

fn 用来声明一个函数。下面是一个 void 函数(没有参数,没有返回值):

  1. fn greet() {
  2. println!("Hi there!");
  3. }

这是一个返回 32 位带符号整数类型的函数。箭头表示它的返回类型:

  1. fn fair_dice_roll() -> i32 {
  2. 4
  3. }

花括号表示一个代码块,且拥有其自己的作用域:

  1. // This prints "in", then "out"
  2. fn main() {
  3. let x = "out";
  4. {
  5. // this is a different `x`
  6. let x = "in";
  7. println!("{}", x);
  8. }
  9. println!("{}", x);
  10. }

代码块也是表示式,最后一个表达式的值是整个代码块的值,以下代码表达的意思一样:

  1. // this:
  2. let x = 42;
  3. // is equivalent to this:
  4. let x = { 42 };

在一个代码块中,可以有多个语句:

  1. let x = {
  2. let y = 1; // first statement
  3. let z = 2; // second statement
  4. y + z // this is the *tail* - what the whole block will evaluate to
  5. };

这也是为什么“省略函数末尾的分号”等同于加上了 retrun,以下是等价的:

  1. fn fair_dice_roll() -> i32 {
  2. return 4;
  3. }
  4. fn fair_dice_roll() -> i32 {
  5. 4
  6. }

if 条件语句也是表达式:

  1. fn fair_dice_roll() -> i32 {
  2. if feeling_lucky {
  3. 6
  4. } else {
  5. 4
  6. }
  7. }

match 也是一个表达式:

  1. fn fair_dice_roll() -> i32 {
  2. match feeling_lucky {
  3. true => 6,
  4. false => 4,
  5. }
  6. }

Dots(点号) 通常用于访问某个对象的字段:

  1. let a = (10, 20);
  2. a.0; // this is 10
  3. let amos = get_some_struct();
  4. amos.nickname; // this is "fasterthanlime"

或者调用对象(值)的方法:

  1. let nick = "fasterthanlime";
  2. nick.len(); // this is 14

双冒号(::)与此类似,但它是对命名空间进行操作。
在这个例子中,std 是一个 crate (~ a library),cmp 是一个 module(~ a source file),而 min 是个函数:

  1. let least = std::cmp::min(3, 8); // this is 3

use 指令可用于从其他命名空间中“引入作用域(scope)”名称:

  1. use std::cmp::min;
  2. let least = min(7, 1); // this is 1

在 use 指令中,花括号还有另一个含义:“globs”。如果我们想同时导入 min 和 max,可以这么做:

  1. // this works:
  2. use std::cmp::min;
  3. use std::cmp::max;
  4. // this also works:
  5. use std::cmp::{min, max};
  6. // this also works!
  7. use std::{cmp::min, cmp::max};

通配符(*)允许从命名空间导入符号:

  1. // this brings `min` and `max` in scope, and many other things
  2. use std::cmp::*;

类型也相当于命名空间,通过 :: 调用其上的方法,以下两种方式等价:

  1. let x = "amos".len(); // this is 4
  2. let x = str::len("amos"); // this is also 4

str 是基本数据类型(primitive type),但在默认情况下,许多非基本数据类型也在当前作用域中(即不需要手动 use 导入)。

  1. // `Vec` is a regular struct, not a primitive type
  2. let v = Vec::new();
  3. // this is exactly the same code, but with the *full* path to `Vec`
  4. let v = std::vec::Vec::new();

为什么可以这么做呢?为了方便,Rust 在每个模块的开头都插入了以下代码(我认为类似 Java 默认导入 java.lang 包):

  1. use std::prelude::v1::*;

以上代码会以此导出许多标识符,比如 Vec、String、Option、Result。

03 结构体

使用 struct 关键字声明结构体:

  1. struct Vec2 {
  2. x: f64, // 64-bit floating point, aka "double precision"
  3. y: f64,
  4. }

可以使用结构语句初始化:

  1. let v1 = Vec2 { x: 1.0, y: 3.0 };
  2. let v2 = Vec2 { y: 2.0, x: 4.0 };
  3. // the order does not matter, only the names do

有一个快捷方式可以从另一个结构体初始化本结构体的其余字段:

  1. let v3 = Vec2 {
  2. x: 14.0,
  3. ..v2
  4. };

这就是所谓的“结构更新语法”,只能发生在最后一个位置,而且不能在其后面再跟一个逗号。
注意其余字段可以表示所有字段:

  1. let v4 = Vec2 { ..v3 };

结构体与元组一样,可以被解构。就像是一个有效的 let 模式:

  1. let (left, right) = slice.split_at(middle);
  2. let v = Vec2 { x: 3.0, y: 6.0 };
  3. let Vec2 { x, y } = v;
  4. // `x` is now 3.0, `y` is now `6.0`

还可以这样:

  1. let Vec2 { x, .. } = v;
  2. // this throws away `v.y`

let 模式可以作为 if 的条件使用:

  1. struct Number {
  2. odd: bool,
  3. value: i32,
  4. }
  5. fn main() {
  6. let one = Number { odd: true, value: 1 };
  7. let two = Number { odd: false, value: 2 };
  8. print_number(one);
  9. print_number(two);
  10. }
  11. fn print_number(n: Number) {
  12. if let Number { odd: true, value } = n {
  13. println!("Odd number: {}", value);
  14. } else if let Number { odd: false, value } = n {
  15. println!("Even number: {}", value);
  16. }
  17. }
  18. // this prints:
  19. // Odd number: 1
  20. // Even number: 2

多分支的 match 也是一种模式,就像 if let:

  1. fn print_number(n: Number) {
  2. match n {
  3. Number { odd: true, value } => println!("Odd number: {}", value),
  4. Number { odd: false, value } => println!("Even number: {}", value),
  5. }
  6. }
  7. // this prints the same as before

match 必须是囊括所有情况的的:至少需要匹配一个分支(Rust 中叫做 arm)。

  1. fn print_number(n: Number) {
  2. match n {
  3. Number { value: 1, .. } => println!("One"),
  4. Number { value: 2, .. } => println!("Two"),
  5. Number { value, .. } => println!("{}", value),
  6. // if that last arm didn't exist, we would get a compile-time error
  7. }
  8. }

如果实在没法包含所有情况,可以增加一个 _ 来捕获所有其他情况,类似其他语言 switch 的 default:

  1. fn print_number(n: Number) {
  2. match n.value {
  3. 1 => println!("One"),
  4. 2 => println!("Two"),
  5. _ => println!("{}", n.value),
  6. }
  7. }

你可以在自定义类型上声明方法:

  1. struct Number {
  2. odd: bool,
  3. value: i32,
  4. }
  5. impl Number {
  6. fn is_strictly_positive(self) -> bool {
  7. self.value > 0
  8. }
  9. }

然后像平常一样使用:

  1. fn main() {
  2. let minus_two = Number {
  3. odd: false,
  4. value: -2,
  5. };
  6. println!("positive? {}", minus_two.is_strictly_positive());
  7. // this prints "positive? false"
  8. }

默认情况下,声明变量后它是不可变的,比如以下 odd 不能被重新赋值:

  1. fn main() {
  2. let n = Number {
  3. odd: true,
  4. value: 17,
  5. };
  6. n.odd = false; // error: cannot assign to `n.odd`,
  7. // as `n` is not declared to be mutable
  8. }

整个结构体值也不能二次赋值:

  1. fn main() {
  2. let n = Number {
  3. odd: true,
  4. value: 17,
  5. };
  6. n = Number {
  7. odd: false,
  8. value: 22,
  9. }; // error: cannot assign twice to immutable variable `n`
  10. }

关键字 mut 可以将变量声明变为可变的:

  1. fn main() {
  2. let mut n = Number {
  3. odd: true,
  4. value: 17,
  5. }
  6. n.value = 19; // all good
  7. }

04 trait(特征)

Traits(特征) 描述的是多种数据类型的共有的东西:(可以类比为其他语言的接口)

  1. trait Signed {
  2. fn is_strictly_negative(self) -> bool;
  3. }

我们可以这么实现:

  • 可以对外部类型实现自定义的 trait;
  • 可以对自定义类型实现外部 trait;
  • 不允许对外部类型实现外部 trait;

这些被称为“孤儿法则”。你如果有点晕,进一步解释一下:

当你为某类型实现某 trait 的时候,必须要求类型或者 trait 至少有一个是在当前 crate 中定义的。你不能为第三方的类型实现第三方的 trait。 此外,这时记得让 trait 的方法可访问(公开)。

可以在我们上面定义的类型(Number)上实现 trait:

  1. impl Signed for Number {
  2. fn is_strictly_negative(self) -> bool {
  3. self.value < 0
  4. }
  5. }
  6. fn main() {
  7. let n = Number { odd: false, value: -44 };
  8. println!("{}", n.is_strictly_negative()); // prints "true"
  9. }

我们对外部类型(foreign type)实现我们定义的 trait:(这里的外部类型使用了基本类型 i32)

  1. impl Signed for i32 {
  2. fn is_strictly_negative(self) -> bool {
  3. self < 0
  4. }
  5. }
  6. fn main() {
  7. let n: i32 = -44;
  8. println!("{}", n.is_strictly_negative()); // prints "true"
  9. }

接着在我们定义的类型上实现一个外部 trait:

  1. // the `Neg` trait is used to overload `-`, the
  2. // unary minus operator.
  3. impl std::ops::Neg for Number {
  4. type Output = Number;
  5. fn neg(self) -> Number {
  6. Number {
  7. value: -self.value,
  8. odd: self.odd,
  9. }
  10. }
  11. }
  12. fn main() {
  13. let n = Number { odd: true, value: 987 };
  14. let m = -n; // this is only possible because we implemented `Neg`
  15. println!("{}", m.value); // prints "-987"
  16. }

impl 代码块通常会对应一个类型,所以在 impl 内,Self 就表示该类型:

  1. impl std::ops::Neg for Number {
  2. type Output = Self;
  3. fn neg(self) -> Self {
  4. Self {
  5. value: -self.value,
  6. odd: self.odd,
  7. }
  8. }
  9. }

有一些 trait 只是作为标记:它们并不是说某个类型实现了某些方法,只是表明某些东西能通过类型完成。例如,i32 实现了 Copy trait,那么以下代码就是可行的:(即 i32 是一个 Copy)

  1. fn main() {
  2. let a: i32 = 15;
  3. let b = a; // `a` is copied
  4. let c = a; // `a` is copied again
  5. }

下面的代码也是能运行的:

  1. fn print_i32(x: i32) {
  2. println!("x = {}", x);
  3. }
  4. fn main() {
  5. let a: i32 = 15;
  6. print_i32(a); // `a` is copied
  7. print_i32(a); // `a` is copied again
  8. }

但是 Number 的结构体并不是一个 Copy,所以下面的代码会报错:

  1. fn main() {
  2. let n = Number { odd: true, value: 51 };
  3. let m = n; // `n` is moved into `m`
  4. let o = n; // error: use of moved value: `n`
  5. }

同样下面的代码也不会正常:

  1. fn print_number(n: Number) {
  2. println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
  3. }
  4. fn main() {
  5. let n = Number { odd: true, value: 51 };
  6. print_number(n); // `n` is moved
  7. print_number(n); // error: use of moved value: `n`
  8. }

但如果 print _ number 采用不可变引用,那么它就可以正常工作:

  1. fn print_number(n: &Number) {
  2. println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
  3. }
  4. fn main() {
  5. let n = Number { odd: true, value: 51 };
  6. print_number(&n); // `n` is borrowed for the time of the call
  7. print_number(&n); // `n` is borrowed again
  8. }

如果一个函数接受一个可变的引用,它也可以工作,但前提是我们的变量绑定也是可变的。

  1. fn invert(n: &mut Number) {
  2. n.value = -n.value;
  3. }
  4. fn print_number(n: &Number) {
  5. println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
  6. }
  7. fn main() {
  8. // this time, `n` is mutable
  9. let mut n = Number { odd: true, value: 51 };
  10. print_number(&n);
  11. invert(&mut n); // `n is borrowed mutably - everything is explicit
  12. print_number(&n);
  13. }

trait 方法也可以通过 self 进行引用或可变引用:

  1. impl std::clone::Clone for Number {
  2. fn clone(&self) -> Self {
  3. Self { ..*self }
  4. }
  5. }

当调用 trait 方法时,接收方隐式地被借用:

  1. fn main() {
  2. let n = Number { odd: true, value: 51 };
  3. let mut m = n.clone();
  4. m.value += 100;
  5. print_number(&n);
  6. print_number(&m);
  7. }

没理解什么意思?以下是等价的,你该理解了:

  1. let m = n.clone();
  2. let m = std::clone::Clone::clone(&n);

像 Copy 这样的标记特征没有方法:

  1. // note: `Copy` requires that `Clone` is implemented too
  2. impl std::clone::Clone for Number {
  3. fn clone(&self) -> Self {
  4. Self { ..*self }
  5. }
  6. }
  7. impl std::marker::Copy for Number {}

现在 Clone 仍然正常:

  1. fn main() {
  2. let n = Number { odd: true, value: 51 };
  3. let m = n.clone();
  4. let o = n.clone();
  5. }

但是 Number 值将不再被移动:

  1. fn main() {
  2. let n = Number { odd: true, value: 51 };
  3. let m = n; // `m` is a copy of `n`
  4. let o = n; // same. `n` is neither moved nor borrowed.
  5. }

有些特性非常常见,它们可以通过 derive 属性自动实现:

  1. #[derive(Clone, Copy)]
  2. struct Number {
  3. odd: bool,
  4. value: i32,
  5. }
  6. // this expands to `impl Clone for Number` and `impl Copy for Number` blocks.

05 泛型

函数支持泛型:

  1. fn foobar<T>(arg: T) {
  2. // do something with `arg`
  3. }

它们可以有多个类型参数,这些参数可以在函数的声明和函数体中使用,而不是具体类型:

  1. fn foobar<L, R>(left: L, right: R) {
  2. // do something with `left` and `right`
  3. }

类型参数通常有约束,因此你可以实际使用它们。
最简单的限制是特征(trait)名:

  1. fn print<T: Display>(value: T) {
  2. println!("value = {}", value);
  3. }
  4. fn print<T: Debug>(value: T) {
  5. println!("value = {:?}", value);
  6. }

类型参数约束有一个更长的语法:

  1. fn print<T>(value: T)
  2. where
  3. T: Display,
  4. {
  5. println!("value = {}", value);
  6. }

约束可能更复杂:它们可能需要一个类型参数来实现多个 traits:

  1. use std::fmt::Debug;
  2. fn compare<T>(left: T, right: T)
  3. where
  4. T: Debug + PartialEq,
  5. {
  6. println!("{:?} {} {:?}", left, if left == right { "==" } else { "!=" }, right);
  7. }
  8. fn main() {
  9. compare("tea", "coffee");
  10. // prints: "tea" != "coffee"
  11. }

泛型函数可以看作是命名空间,包含无数具有不同具体类型的函数。
与 crates、modules 和 类型一样,泛型函数可以通过 :: 指定具体类型:

  1. fn main() {
  2. use std::any::type_name;
  3. println!("{}", type_name::<i32>()); // prints "i32"
  4. println!("{}", type_name::<(f64, char)>()); // prints "(f64, char)"
  5. }

这就是所谓的 turbofish 语法,因为 ::<> 看起来像一条鱼。
结构体也可以是泛型:

  1. struct Pair<T> {
  2. a: T,
  3. b: T,
  4. }
  5. fn print_type_name<T>(_val: &T) {
  6. println!("{}", std::any::type_name::<T>());
  7. }
  8. fn main() {
  9. let p1 = Pair { a: 3, b: 9 };
  10. let p2 = Pair { a: true, b: false };
  11. print_type_name(&p1); // prints "Pair<i32>"
  12. print_type_name(&p2); // prints "Pair<bool>"
  13. }

标准库类型 Vec(堆分配数组)是泛型的:

  1. fn main() {
  2. let mut v1 = Vec::new();
  3. v1.push(1);
  4. let mut v2 = Vec::new();
  5. v2.push(false);
  6. print_type_name(&v1); // prints "Vec<i32>"
  7. print_type_name(&v2); // prints "Vec<bool>"
  8. }

06 宏

说到 Vec,它附带了一个宏 “Vec 字面值”。

  1. fn main() {
  2. let v1 = vec![1, 2, 3];
  3. let v2 = vec![true, false, true];
  4. print_type_name(&v1); // prints "Vec<i32>"
  5. print_type_name(&v2); // prints "Vec<bool>"
  6. }

所有的 name!(),name![] 或 name!{} 都是在调用宏。宏最终会展开为常规代码。
所以,println 也是一个宏。

  1. fn main() {
  2. println!("{}", "Hello there!");
  3. }

这个会展开为和下面有相同效果的代码:

  1. fn main() {
  2. use std::io::{self, Write};
  3. io::stdout().lock().write_all(b"Hello there!\n").unwrap();
  4. }

panic 也是一个宏。如果调用了它,会使用错误消息以及错误的文件名/行号强制停止执行:

  1. fn main() {
  2. panic!("This panics");
  3. }
  4. // output: thread 'main' panicked at 'This panics', src/main.rs:3:5

有些方法也会引起 panic。例如,Option 类型可以包含某些内容,也可以不包含任何内容。如果调用 Unwrap(),但它不包含任何内容,就会 panic:

  1. fn main() {
  2. let o1: Option<i32> = Some(128);
  3. o1.unwrap(); // this is fine
  4. let o2: Option<i32> = None;
  5. o2.unwrap(); // this panics!
  6. }
  7. // output: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21

07 枚举

Option 不是结构体,而是带有两个变体的枚举:

  1. enum Option<T> {
  2. None,
  3. Some(T),
  4. }
  5. impl<T> Option<T> {
  6. fn unwrap(self) -> T {
  7. // enums variants can be used in patterns:
  8. match self {
  9. Self::Some(t) => t,
  10. Self::None => panic!(".unwrap() called on a None option"),
  11. }
  12. }
  13. }
  14. use self::Option::{None, Some};
  15. fn main() {
  16. let o1: Option<i32> = Some(128);
  17. o1.unwrap(); // this is fine
  18. let o2: Option<i32> = None;
  19. o2.unwrap(); // this panics!
  20. }
  21. // output: thread 'main' panicked at '.unwrap() called on a None option', src/main.rs:11:27

Result 也是一个枚举,它可以包含一些东西,或包含一个错误:

  1. enum Result<T, E> {
  2. Ok(T),
  3. Err(E),
  4. }

如果 Result 包含错误时,进行 unwrap 操作会 panic。

08 生命周期

变量绑定有一个“生命周期(lifetime)”:

  1. fn main() {
  2. // `x` doesn't exist yet
  3. {
  4. let x = 42; // `x` starts existing
  5. println!("x = {}", x);
  6. // `x` stops existing
  7. }
  8. // `x` no longer exists
  9. }

类似地,引用也有生命周期:

  1. fn main() {
  2. // `x` doesn't exist yet
  3. {
  4. let x = 42; // `x` starts existing
  5. let x_ref = &x; // `x_ref` starts existing - it borrows `x`
  6. println!("x_ref = {}", x_ref);
  7. // `x_ref` stops existing
  8. // `x` stops existing
  9. }
  10. // `x` no longer exists
  11. }

引用的生命周期你不能超过其借用的变量绑定的生命周期:

  1. fn main() {
  2. let x_ref = {
  3. let x = 42;
  4. &x
  5. };
  6. println!("x_ref = {}", x_ref);
  7. // error: `x` does not live long enough
  8. }

一个变量绑定可以被多次借用:

  1. fn main() {
  2. let x = 42;
  3. let x_ref1 = &x;
  4. let x_ref2 = &x;
  5. let x_ref3 = &x;
  6. println!("{} {} {}", x_ref1, x_ref2, x_ref3);
  7. }

在被借用期间,变量绑定不能被修改:

  1. fn main() {
  2. let mut x = 42;
  3. let x_ref = &x;
  4. x = 13;
  5. println!("x_ref = {}", x_ref);
  6. // error: cannot assign to `x` because it is borrowed
  7. }

当存在不可变借用时,变量不能再次作为可变借用。

  1. fn main() {
  2. let mut x = 42;
  3. let x_ref1 = &x;
  4. let x_ref2 = &mut x;
  5. // error: cannot borrow `x` as mutable because it is also borrowed as immutable
  6. println!("x_ref1 = {}", x_ref1);
  7. }

函数参数中的引用也有生命周期:

  1. fn print(x: &i32) {
  2. // `x` is borrowed (from the outside) for the
  3. // entire time this function is called.
  4. }

带有引用参数的函数可以使用具有不同生命期的 borrows 来调用,所以:

  • 所有参数是引用的函数,它们实际上都是泛型函数;
  • 生命周期就是泛型参数;

生命周期名以单引号 ' 开头:

  1. // elided (non-named) lifetimes:
  2. fn print(x: &i32) {}
  3. // named lifetimes:
  4. fn print<'a>(x: &'a i32) {}

这允许返回的引用的生命周期依赖于参数的生命周期:

  1. struct Number {
  2. value: i32,
  3. }
  4. fn number_value<'a>(num: &'a Number) -> &'a i32 {
  5. &num.value
  6. }
  7. fn main() {
  8. let n = Number { value: 47 };
  9. let v = number_value(&n);
  10. // `v` borrows `n` (immutably), thus: `v` cannot outlive `n`.
  11. // While `v` exists, `n` cannot be mutably borrowed, mutated, moved, etc.
  12. }

当只有一个输入生命周期时,它不需要命名,所有的东西都具有相同的生命周期,因此下面的两个函数是等价的:

  1. fn number_value<'a>(num: &'a Number) -> &'a i32 {
  2. &num.value
  3. }
  4. fn number_value(num: &Number) -> &i32 {
  5. &num.value
  6. }

当有引用字段时,结构体也是基于生命周期的泛型:

  1. struct NumRef<'a> {
  2. x: &'a i32,
  3. }
  4. fn main() {
  5. let x: i32 = 99;
  6. let x_ref = NumRef { x: &x };
  7. // `x_ref` cannot outlive `x`, etc.
  8. }

同样的代码,但是有一个额外的函数:

  1. struct NumRef<'a> {
  2. x: &'a i32,
  3. }
  4. fn as_num_ref<'a>(x: &'a i32) -> NumRef<'a> {
  5. NumRef { x: &x }
  6. }
  7. fn main() {
  8. let x: i32 = 99;
  9. let x_ref = as_num_ref(&x);
  10. // `x_ref` cannot outlive `x`, etc.
  11. }

相同的代码,但省略了生命期:

  1. struct NumRef<'a> {
  2. x: &'a i32,
  3. }
  4. fn as_num_ref(x: &i32) -> NumRef<'_> {
  5. NumRef { x: &x }
  6. }
  7. fn main() {
  8. let x: i32 = 99;
  9. let x_ref = as_num_ref(&x);
  10. // `x_ref` cannot outlive `x`, etc.
  11. }

Impl 块也可以是基于生命周期的泛型:

  1. impl<'a> NumRef<'a> {
  2. fn as_i32_ref(&'a self) -> &'a i32 {
  3. self.x
  4. }
  5. }
  6. fn main() {
  7. let x: i32 = 99;
  8. let x_num_ref = NumRef { x: &x };
  9. let x_i32_ref = x_num_ref.as_i32_ref();
  10. // neither ref can outlive `x`
  11. }

但你也可以在这里省略:

  1. impl<'a> NumRef<'a> {
  2. fn as_i32_ref(&self) -> &i32 {
  3. self.x
  4. }
  5. }

如果你从来不需要这个名字,你可以省去更多:

  1. impl NumRef<'_> {
  2. fn as_i32_ref(&self) -> &i32 {
  3. self.x
  4. }
  5. }

有一个特殊的生命周期,名为 'static,它对整个程序的生命周期都有效。
字符串字面量就是 'static

  1. struct Person {
  2. name: &'static str,
  3. }
  4. fn main() {
  5. let p = Person {
  6. name: "fasterthanlime",
  7. };
  8. }

但非字面量字符串并不是 static 的:

  1. struct Person {
  2. name: &'static str,
  3. }
  4. fn main() {
  5. let name = format!("fasterthan{}", "lime");
  6. let p = Person { name: &name };
  7. // error: `name` does not live long enough
  8. }

在最后一个示例中,局部变量 name 并不是 &'static str,而是 String。它是动态分配的,并且会在不需要释放。它的生命周期小于整个程序(即使它碰巧在 main 中)。
要在 Person 中存储非 'static 字符串,它需要:
A)基于生命周期的泛型:

  1. struct Person<'a> {
  2. name: &'a str,
  3. }
  4. fn main() {
  5. let name = format!("fasterthan{}", "lime");
  6. let p = Person { name: &name };
  7. // `p` cannot outlive `name`
  8. }


B)取得字符串所有权

  1. struct Person {
  2. name: String,
  3. }
  4. fn main() {
  5. let name = format!("fasterthan{}", "lime");
  6. let p = Person { name: name };
  7. // `name` was moved into `p`, their lifetimes are no longer tied.
  8. }

在 struct literal 中,当一个字段被设置为同名的变量绑定时:

  1. let p = Person { name: name };

它可以这样简写:

  1. let p = Person { name };

09 所有权

对于 Rust 中的许多类型,存在有所有权和没有所有权之分:

  • Strings:String 是有所有权的;$str 是一个引用;
  • Paths:PathBuf 是有所有权的;$Path 是一个引用;
  • Collections:Vec<T> 是有所有权的;&[T] 是一个引用;

Rust 有切片类型(slice)—— 它们是对多个连续元素的引用。
你可以借用 Vector 的一个切片,例如:

  1. fn main() {
  2. let v = vec![1, 2, 3, 4, 5];
  3. let v2 = &v[2..4];
  4. println!("v2 = {:?}", v2);
  5. }
  6. // output:
  7. // v2 = [3, 4]

以上并不神奇。索引操作符(foo[index])被 Index 和 IndexMut 特征重载。
.. 语法是 range 字面值,range 是标准库中定义的几个结构体。
它们可以是半闭半开的,如果前面有 = ,那么它们就是闭区间。

  1. fn main() {
  2. // 0 or greater
  3. println!("{:?}", (0..).contains(&100)); // true
  4. // strictly less than 20
  5. println!("{:?}", (..20).contains(&20)); // false
  6. // 20 or less than 20
  7. println!("{:?}", (..=20).contains(&20)); // true
  8. // only 3, 4, 5
  9. println!("{:?}", (3..6).contains(&4)); // true
  10. }

借用规则适用于切片。

  1. fn tail(s: &[u8]) -> &[u8] {
  2. &s[1..]
  3. }
  4. fn main() {
  5. let x = &[1, 2, 3, 4, 5];
  6. let y = tail(x);
  7. println!("y = {:?}", y);
  8. }

这和下面的一样:

  1. fn tail<'a>(s: &'a [u8]) -> &'a [u8] {
  2. &s[1..]
  3. }

以下是合法的:

  1. fn main() {
  2. let y = {
  3. let x = &[1, 2, 3, 4, 5];
  4. tail(x)
  5. };
  6. println!("y = {:?}", y);
  7. }

但因为 [1,2,3,4,5] 是一个 'static 数组。所以,以下是非法的:

  1. fn main() {
  2. let y = {
  3. let v = vec![1, 2, 3, 4, 5];
  4. tail(&v)
  5. // error: `v` does not live long enough
  6. };
  7. println!("y = {:?}", y);
  8. }

因为 Vector 是在堆分配的,而且它的生命周期是非 'static 的。
&str 实际上是切片。

  1. fn file_ext(name: &str) -> Option<&str> {
  2. // this does not create a new string - it returns
  3. // a slice of the argument.
  4. name.split(".").last()
  5. }
  6. fn main() {
  7. let name = "Read me. Or don't.txt";
  8. if let Some(ext) = file_ext(name) {
  9. println!("file extension: {}", ext);
  10. } else {
  11. println!("no file extension");
  12. }
  13. }

所以,借用规则也适用它:

  1. fn main() {
  2. let ext = {
  3. let name = String::from("Read me. Or don't.txt");
  4. file_ext(&name).unwrap_or("")
  5. // error: `name` does not live long enough
  6. };
  7. println!("extension: {:?}", ext);
  8. }

10 错误处理

可能失败的函数通常返回一个 Result:

  1. fn main() {
  2. let s = std::str::from_utf8(&[240, 159, 141, 137]);
  3. println!("{:?}", s);
  4. // prints: Ok("🍉")
  5. let s = std::str::from_utf8(&[195, 40]);
  6. println!("{:?}", s);
  7. // prints: Err(Utf8Error { valid_up_to: 0, error_len: Some(1) })
  8. }

如果你想在失败的情况下 panic,你可以调用 unwrap():

  1. fn main() {
  2. let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
  3. println!("{:?}", s);
  4. // prints: "🍉"
  5. let s = std::str::from_utf8(&[195, 40]).unwrap();
  6. // prints: thread 'main' panicked at 'called `Result::unwrap()`
  7. // on an `Err` value: Utf8Error { valid_up_to: 0, error_len: Some(1) }',
  8. // src/libcore/result.rs:1165:5
  9. }

或者 .expect(),它可以提供自定义错误信息。

  1. fn main() {
  2. let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8");
  3. // prints: thread 'main' panicked at 'valid utf-8: Utf8Error
  4. // { valid_up_to: 0, error_len: Some(1) }', src/libcore/result.rs:1165:5
  5. }

或者你也可以使用 match:

  1. fn main() {
  2. match std::str::from_utf8(&[240, 159, 141, 137]) {
  3. Ok(s) => println!("{}", s),
  4. Err(e) => panic!(e),
  5. }
  6. // prints 🍉
  7. }

也可以使用 if let:

  1. fn main() {
  2. if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) {
  3. println!("{}", s);
  4. }
  5. // prints 🍉
  6. }

或者你可以抛出这样的错误:

  1. fn main() -> Result<(), std::str::Utf8Error> {
  2. match std::str::from_utf8(&[240, 159, 141, 137]) {
  3. Ok(s) => println!("{}", s),
  4. Err(e) => return Err(e),
  5. }
  6. Ok(())
  7. }

或者你可以使用 ? 来简洁地完成它:

  1. fn main() -> Result<(), std::str::Utf8Error> {
  2. let s = std::str::from_utf8(&[240, 159, 141, 137])?;
  3. println!("{}", s);
  4. Ok(())
  5. }

操作符 * 可用于解引用,但是访问字段或调用方法不需要解引用:

  1. struct Point {
  2. x: f64,
  3. y: f64,
  4. }
  5. fn main() {
  6. let p = Point { x: 1.0, y: 3.0 };
  7. let p_ref = &p;
  8. println!("({}, {})", p_ref.x, p_ref.y);
  9. }
  10. // prints `(1, 3)`

而且你只能在类型为 Copy 的情况下才能这么做:

  1. struct Point {
  2. x: f64,
  3. y: f64,
  4. }
  5. fn negate(p: Point) -> Point {
  6. Point {
  7. x: -p.x,
  8. y: -p.y,
  9. }
  10. }
  11. fn main() {
  12. let p = Point { x: 1.0, y: 3.0 };
  13. let p_ref = &p;
  14. negate(*p_ref);
  15. // error: cannot move out of `*p_ref` which is behind a shared reference
  16. }
  1. // now `Point` is `Copy`
  2. #[derive(Clone, Copy)]
  3. struct Point {
  4. x: f64,
  5. y: f64,
  6. }
  7. fn negate(p: Point) -> Point {
  8. Point {
  9. x: -p.x,
  10. y: -p.y,
  11. }
  12. }
  13. fn main() {
  14. let p = Point { x: 1.0, y: 3.0 };
  15. let p_ref = &p;
  16. negate(*p_ref); // ...and now this works
  17. }

11 闭包

闭包是 Fn、 FnMut 或 FnOnce 类型的函数,包含一些捕获的上下文。
它们的参数是一对管道(|)中以逗号分隔的名称列表。它们不需要大括号,除非您希望有多个语句。

  1. fn for_each_planet<F>(f: F)
  2. where F: Fn(&'static str)
  3. {
  4. f("Earth");
  5. f("Mars");
  6. f("Jupiter");
  7. }
  8. fn main() {
  9. for_each_planet(|planet| println!("Hello, {}", planet));
  10. }
  11. // prints:
  12. // Hello, Earth
  13. // Hello, Mars
  14. // Hello, Jupiter

借款规则也适用于它们:

  1. fn for_each_planet<F>(f: F)
  2. where F: Fn(&'static str)
  3. {
  4. f("Earth");
  5. f("Mars");
  6. f("Jupiter");
  7. }
  8. fn main() {
  9. let greeting = String::from("Good to see you");
  10. for_each_planet(|planet| println!("{}, {}", greeting, planet));
  11. // our closure borrows `greeting`, so it cannot outlive it
  12. }

例如,这样做不会奏效:

  1. fn for_each_planet<F>(f: F)
  2. where F: Fn(&'static str) + 'static // `F` must now have "'static" lifetime
  3. {
  4. f("Earth");
  5. f("Mars");
  6. f("Jupiter");
  7. }
  8. fn main() {
  9. let greeting = String::from("Good to see you");
  10. for_each_planet(|planet| println!("{}, {}", greeting, planet));
  11. // error: closure may outlive the current function, but it borrows
  12. // `greeting`, which is owned by the current function
  13. }

但这样可以:

  1. fn main() {
  2. let greeting = String::from("You're doing great");
  3. for_each_planet(move |planet| println!("{}, {}", greeting, planet));
  4. // `greeting` is no longer borrowed, it is *moved* into
  5. // the closure.
  6. }

需要可变地借用 FnMut 才能调用它,因此一次只能调用它一次。
这是合法的:

  1. fn foobar<F>(f: F)
  2. where F: Fn(i32) -> i32
  3. {
  4. println!("{}", f(f(2)));
  5. }
  6. fn main() {
  7. foobar(|x| x * 2);
  8. }
  9. // output: 8

但以下不行:

  1. fn foobar<F>(mut f: F)
  2. where F: FnMut(i32) -> i32
  3. {
  4. println!("{}", f(f(2)));
  5. // error: cannot borrow `f` as mutable more than once at a time
  6. }
  7. fn main() {
  8. foobar(|x| x * 2);
  9. }

不过下面又是合法的:

  1. fn foobar<F>(mut f: F)
  2. where F: FnMut(i32) -> i32
  3. {
  4. let tmp = f(2);
  5. println!("{}", f(tmp));
  6. }
  7. fn main() {
  8. foobar(|x| x * 2);
  9. }
  10. // output: 8

FnMut 的存在是因为一些闭包可变地借用了局部变量:

  1. fn foobar<F>(mut f: F)
  2. where F: FnMut(i32) -> i32
  3. {
  4. let tmp = f(2);
  5. println!("{}", f(tmp));
  6. }
  7. fn main() {
  8. let mut acc = 2;
  9. foobar(|x| {
  10. acc += 1;
  11. x * acc
  12. });
  13. }
  14. // output: 24

这些闭包不能传递给期待 Fn 的函数:

  1. fn foobar<F>(f: F)
  2. where F: Fn(i32) -> i32
  3. {
  4. println!("{}", f(f(2)));
  5. }
  6. fn main() {
  7. let mut acc = 2;
  8. foobar(|x| {
  9. acc += 1;
  10. // error: cannot assign to `acc`, as it is a
  11. // captured variable in a `Fn` closure.
  12. // the compiler suggests "changing foobar
  13. // to accept closures that implement `FnMut`"
  14. x * acc
  15. });
  16. }

FnOnce 闭包只能调用一次。它们的存在是因为一些闭包移出了捕获时移动的变量:

  1. fn foobar<F>(f: F)
  2. where F: FnOnce() -> String
  3. {
  4. println!("{}", f());
  5. }
  6. fn main() {
  7. let s = String::from("alright");
  8. foobar(move || s);
  9. // `s` was moved into our closure, and our
  10. // closures moves it to the caller by returning
  11. // it. Remember that `String` is not `Copy`.
  12. }

这是自然地强制执行的,因为 FnOnce 闭包需要移动才能被调用。
以下例子是无效的:

  1. fn foobar<F>(f: F)
  2. where F: FnOnce() -> String
  3. {
  4. println!("{}", f());
  5. println!("{}", f());
  6. // error: use of moved value: `f`
  7. }

而且,如果你需要有说服力的证明我们的关闭确实会发生变化,这也是非法的:

  1. fn main() {
  2. let s = String::from("alright");
  3. foobar(move || s);
  4. foobar(move || s);
  5. // use of moved value: `s`
  6. }

不过这样是正常的:

  1. fn main() {
  2. let s = String::from("alright");
  3. foobar(|| s.clone());
  4. foobar(|| s.clone());
  5. }

下面是一个包含两个参数的闭包:

  1. fn foobar<F>(x: i32, y: i32, is_greater: F)
  2. where F: Fn(i32, i32) -> bool
  3. {
  4. let (greater, smaller) = if is_greater(x, y) {
  5. (x, y)
  6. } else {
  7. (y, x)
  8. };
  9. println!("{} is greater than {}", greater, smaller);
  10. }
  11. fn main() {
  12. foobar(32, 64, |x, y| x > y);
  13. }

以下是一个闭包,但忽略了它的两个参数:

  1. fn main() {
  2. foobar(32, 64, |_, _| panic!("Comparing is futile!"));
  3. }

下面是一个有点令人担忧的闭包:

  1. fn countdown<F>(count: usize, tick: F)
  2. where F: Fn(usize)
  3. {
  4. for i in (1..=count).rev() {
  5. tick(i);
  6. }
  7. }
  8. fn main() {
  9. countdown(3, |i| println!("tick {}...", i));
  10. }
  11. // output:
  12. // tick 3...
  13. // tick 2...
  14. // tick 1...

下面是一个 toilet 闭包:

  1. fn main() {
  2. countdown(3, |_| ());
  3. }

之所以这么叫是因为 |_| () 看起来像个厕所(toilet )。
任何可迭代的东西都可以在 for in 循环中使用。
我们刚刚看到一个 range 被使用,但是它也适用于一个 Vec:

  1. fn main() {
  2. for i in vec![52, 49, 21] {
  3. println!("I like the number {}", i);
  4. }
  5. }

或者切片:

  1. fn main() {
  2. for i in &[52, 49, 21] {
  3. println!("I like the number {}", i);
  4. }
  5. }
  6. // output:
  7. // I like the number 52
  8. // I like the number 49
  9. // I like the number 21

或者是迭代器:

  1. fn main() {
  2. // note: `&str` also has a `.bytes()` iterator.
  3. // Rust's `char` type is a "Unicode scalar value"
  4. for c in "rust".chars() {
  5. println!("Give me a {}", c);
  6. }
  7. }
  8. // output:
  9. // Give me a r
  10. // Give me a u
  11. // Give me a s
  12. // Give me a t

即使迭代的项目被 filter、map 或 flat:

  1. fn main() {
  2. for c in "SuRPRISE INbOUND"
  3. .chars()
  4. .filter(|c| c.is_lowercase())
  5. .flat_map(|c| c.to_uppercase())
  6. {
  7. print!("{}", c);
  8. }
  9. println!();
  10. }
  11. // output: UB

你可以从函数返回一个闭包:

  1. fn make_tester(answer: String) -> impl Fn(&str) -> bool {
  2. move |challenge| {
  3. challenge == answer
  4. }
  5. }
  6. fn main() {
  7. // you can use `.into()` to perform conversions
  8. // between various types, here `&'static str` and `String`
  9. let test = make_tester("hunter2".into());
  10. println!("{}", test("******"));
  11. println!("{}", test("hunter2"));
  12. }

你甚至可以将对某个函数参数的引用移动到它返回的闭包中:

  1. fn make_tester<'a>(answer: &'a str) -> impl Fn(&str) -> bool + 'a {
  2. move |challenge| {
  3. challenge == answer
  4. }
  5. }
  6. fn main() {
  7. let test = make_tester("hunter2");
  8. println!("{}", test("*******"));
  9. println!("{}", test("hunter2"));
  10. }
  11. // output:
  12. // false
  13. // true

或者,用省略的生命周期:

  1. fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ {
  2. move |challenge| {
  3. challenge == answer
  4. }
  5. }

总结

以上就是这个 30 分钟教程的全部内容。作者认为,通过以上的学习,你应该能够阅读网上找到的大部分 Rust 代码了。
读 Rust 代码和写 Rust 代码很不一样。一方面,你不是读一个问题的解决方案,而是实际解决它;另一方面,Rust 编译器很强大,可以给你很多帮助。
对于这篇教程中故意的错误代码,Rust 编译器总是能非常好的提示你,同时给你很好的建议。

原文链接:https://fasterthanli.me/articles/a-half-hour-to-learn-rust Rust 编程指北编译,非直接翻译。

实话说,这个教程不可能 30 分钟学完,即使学完了,肯定也没入门。不过通过这些代码,自己动手实践,对学习 Rust 还是有帮助的!