7540 字
38 分钟
半小时速通 Rust
2024-02-17

编程语言不学Rust,就如同游戏不玩原神

本文翻译自A half-hour to learn Rust


let#

let用于声明变量绑定(binding)

let x; // 声明 x
x = 42; // 将 42 赋值给 x

也可以写成一行:

let x = 42;

可以通过:来显式指定变量类型,即类型注释:

let x: i32; // `i32` 为 32 位有符号整型
x = 42;
// 在Rust中,整型有 i8, i16, i32, i64, 128
// 以及对应的无符号类型 u8, u16, u32, u64, u128

也可以写成一行:

let x: i32 = 42;

如果你在声明变量后,初始化前使用变量,则会被编译器阻止:

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

这样则没问题:

let x;
x = 42;
foobar(x); // `x` 的类型将会根据此处推断

下划线_是一个特殊的变量名称,即缺省名称。通常使用_说明丢弃一些东西:

let _ = 42; // 将不会做任何事,因为 42 是一个常量
let _ = get_thing(); // 调用 `get_thing` 但丢弃返回值

变量可以以下划线开头,除了编译器不会警告它们未被使用之外与普通变量名一样:

// `_x` 可能最终会被用到,但是目前代码还没有完成,并且当前不想看到编译器对其的警告
let _x = 42;

可以引入相同名称的变量绑定,进而隐藏原先的变量绑定:

let x = 13;
let x = x + 3;
// 之后使用 `x` 仅指代第二个 `x`, 第一个 `x` 不再存在

Tuple#

元组(Tuple)可以将其视作不同类型值的定长集合:

let pair = ('a', 17);
pair.0 // 'a'
pair.1 // 17

若要给元组添加类型注释,则可以:

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

在进行赋值的时候元组可以被解构,即可以分解为独立的字段:

let (some_char, some_int) = ('a', 17);

在函数返回元组时非常有用:

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

当然,在结构元组的时候也可以使用_来丢弃其中一部分:

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

语句和表达式#

分号标志着语句的结束:

let x = 3;
let y = 5;
let z = x + y;

这意味着语句可以跨越多行:

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

(后文会对其作讲解)

fn#

fn用于声明函数 这是一个无返回值的函数:

fn greet() {
println!("Hi there!");
}

这是一个返回 32 位有符号整型的函数。用箭头来表示其返回值类型:

fn fair_dice_roll() -> i32 {
4
}

花括号对{}用于声明块(Block),它有自己的作用域:

// 该程序会先输出 "in" 然后是 "out"
fn main() {
let x = "out";
{
// 此处是另一个 `x`
let x = "in";
println!("{}", x);
}
println!("{}", x);
}

块也是表达式,这意味着其计算结果也是一个值。

// 以下两行是等价的
let x = 42;
let x = { 42 };

在一个块内可以有多条语句:

let x = {
let y = 1; // 第一条语句
let z = 2; // 第二条语句
y + z // 这里是*尾巴* - 整个块会被认定的表达式
};

这就是为什么”省略函数末尾的分号“与return是相同的,即以下写法是等效的:

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; // 10
let amos = get_some_struct();
amos.nickname; // "fasterthanlime"

或者对值调用方法:

let nick = "fasterthanlime";
nick.len(); // 14

双冒号::与其类似,但是它使用在命名空间上。 在此示例中,std是一个包(crate)(或者说库),cmp是一个模块(module)(或者说源文件),以及min是一个函数(function):

let least = std::cmp::min(3, 8); // 3

use可用于从其他命名空间引入名称到作用域:

use std::cmp::min;
let least = min(7, 1); // 1

使用use时,花括号还有另一种含义,即 glob。如果我们想同时导入minmax,我们可以使用以下任意一种方式:

use std::cmp::min;
use std::cmp::max;
use std::cmp::{min, max};
use std::{cmp::min, cmp::max};

通配符*允许你从命名空间导入所有符号:

// 这将会将 `min` 和 `max` 以及许多其他东西导入到当前作用域
use std::cmp::*;

类型也是命名空间,方法可以作为常规函数调用:

let x = "amos".len(); // 4
let x = str::len("amos"); // 4

str是原始类型,但默认情况下许多非原始类型也包含在作用域内:

// `Vec` 是一个常规的结构体,不是原始类型
let v = Vec::new();
// 与上面的代码相同,但是使用了 `Vec` 的完整路径
let v = std::vec::Vec::new();

这之所以有效是因为 Rust 会在每个模块的开头插入以下代码:

use std::prelude::v1::*;

这会重新导出很多符号,例如VecStringOptionResult

struct#

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

struct Vec2 {
x: f64, // 64 位浮点数,即双精度
y: f64,
}

它们可以通过结构体字面量初始化:

let v1 = Vec2 { x: 1.0, y: 3.0};
let v2 = Vec2 { y: 2.0, x: 4.0};
// 顺序不需要关注,只需要关注名称

有一种简便写法可以从另一个结构体初始化剩余的字段:

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` 为 3.0, `y` 为 6.0

以及这样:

let Vec2 { x, ..} = v;
// 将会忽略 `v.y`

模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 match 表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:

  • 字面值
  • 解构的数组、枚举、结构体或者元组
  • 变量
  • 通配符
  • 占位符 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);
}
}
// 将会输出:
// 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),
}
}
// 输出与之前相同

match是穷尽式的,即至少有一个分支被匹配。

fn print_number(n: Number) {
match n {
Number { value: 1, .. } => println!("One"),
Number { value: 2, .. } => println!("Two"),
Number { value, .. } => println!("{}", value),
// 如果最后一个分支不存在,那么编译器将会报错
}
}

如果很难做到匹配所有分支,那么则可以使用_来匹配所有剩余的情况:

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());
// 输出 "positive? false"
}

mut#

默认情况下,变量绑定是不可变的,这意味着它们的内部状态不能被改变:

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; // 一切顺利
}

特征#

特征(trait)是指多种类型可以共有的共同点:

trait Signed {
fn is_strictly_negative(self) -> bool;
}

你可以实现:

  • 对任何人的类型使用你的特征
  • 将任何人的特征用于你的类型
  • 不能将外来特征用于外来类型 这被称作孤儿规则(orphan rules),更为规范的定义是如果你想要为类型 A 实现特征 T,那么 A 或者 T 至少有一个是在当前作用域中定义的。 这是一个将我们的特征用在我们的类型上的实现:
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()); // 输出 "true"
}

将我们的特征用在外来类型(甚至是原始类型)上:

impl Signed for i32 {
fn is_strictly_negative(self) -> bool {
self < 0
}
}
fn main() {
let n: i32 = -44;
println!("{}", n.is_strictly_negative()); // 输出 "true"
}

将外来特征用在我们的类型上:

// `Neg` 特征用于重载 `-`,即一元减运算符
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; // 因为我们实现了 `Neg` 所以可以这样做
println!("{}", m.value); // 输出 "-987"
}

一个impl块使用代表一种类型,所以在该块里Self表示该类型:

impl std::ops::Neg for Number {
type Output = Self;
fn neg(self) -> Self {
Self {
value: -self.value,
odd: self.odd,
}
}
}

有一些特征是标记(marker),它们并不是指该类型实现了某些方法,而是指可以用该类型完成某些事情。 例如,i32实现了特征Copy(简单说i32Copy),所以以下是可行的:

fn main() {
let a: i32 = 15;
let b = a; // `a` 被复制
let c = a; // `a` 再次被复制
}

同时以下也可行:

fn print_i32(x: i32) {
println!("x = {}", x);
}
fn main() {
let a: i32 = 15;
print_i32(a); // `a` 被复制
print_i32(a); // `a` 再次被复制
}

但是Number结构体不是Copy,所以以下是不可行的:

fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `n` 被移动至 `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` 被移动
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` 被引用给这次调用
print_number(&n); // `n` 再次被引用
}

如果函数采用可变引用,那么也可以工作,前提是我们的变量绑定也是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() {
// 这一次 `n` 是可变的
let mut n = Number { odd: true, value: 51 };
print_number(&n);
invert(&mut n); // `n 被可变引用,一切都是明确的
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这样的标记特征没有方法:

// 注意:`Copy` 需要 `Clone` 也被实现
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}

现在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: trie, value: 51 };
let m = n; // `m` 是 `n` 的一个拷贝
let o = n; // 同样,`n` 不再会被移动或引用
}

有些特征非常常见,可以使用derive属性自动实现:

#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
// 这将会展开成 `impl Clone for Number` 和 `impl Copy for Number` 块

泛型(Generics)#

函数可以是泛型化的:

fn foobar<T>(arg: T) {
// 与 `arg` 相关的操作
}

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

fn foobar<L, R>(left: L, right: R) {
// 与 `left` 和 `right` 相关的操作
}

类型参数通常由约束(constraint),所以实际上你可以用它们来做一些事情。 最简单的约束是特征名称:

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");
// 输出 "tea != coffee"
}

泛型函数可以被认为是命名空间,包含无穷个具有不同具体类型的函数。 与包,模块和类型相同,可以对泛型函数使用::

fn main() {
use std::any::type_name;
println!("{}", type_name::<i32>()); // 输出 "i32"
println!("{}", type_name::<(f64, char)>()); // 输出 "(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);
print_type_name(&p2);
}

标准库类型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,它带有一个宏,可以提供或多或少的”vec 字面量“:

fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![true, false, true];
print_type_name(&v1);
print_type_name(&v2);
}

所有的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也是一个宏,它会猛烈地停止执行并显示错误消息和文件名 / 行号:

fn main() {
panic!("This panics");
}

有些方法也会出现panic。例如,Option类型可以包含某些内容,也可以不包含任何内容。如果.unwrap()被调用,并且它不包括任何内容,那么就会发生panic

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),有两个成员:

num Option<T> {
None,
Some(T),
}
impl<T> Option<T> {
fn unwrap(self) -> T {
// 枚举成员可以被用在模式中:
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),
}

若调用unwrap时包含错误,那么它也会panic

变量绑定#

变量绑定拥有一个”生命周期“:

fn main() {
// `x` 还不存在
{
let x = 42; // `x` 开始存在
println!("x = {}", x);
// `x` 停止存在
}
// `x` 不再存在
}

同样,引用也有生命周期:

fn main() {
// `x` 还不存在
{
let x = 42; // `x` 开始存在
let x_ref = &x; // `x_ref` 开始存在 - 它引用 `x`
println!("x_ref = {}", x_ref);
// `x_ref` 停止存在
// `x` 停止存在
}
// `x` 不再存在
}

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

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` 是从外部被引用的,可用在该次函数调用的全过程
}

具有引用参数的函数可以通过具有不同声明周期的引用来调用,因此:

  • 所有接受引用的函数都是泛型化的
  • 生命周期是泛型参数 生命周期的参数以单引号'开头:
// 隐式(匿名)生命周期
fn print(x: &i32) {}
// 命名生命周期
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` 是 `n` 的不可变引用,因此 `v` 的生命周期不能超过 `n`.
// 当 `v` 存在时,`n` 不能被可变引用,改变,移动等
}

当只有一个生命周期输入时,不需要命名,所有变量都具有相同的生命周期,因此以下两个函数是等效的:

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` 的生命周期不能长于 `x`
}

增加个相关函数:

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 = NumRef { x: &x };
// `x_ref` 的生命周期不能长于 `x`
}

在函数中省写生命周期:

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 = NumRef { x: &x };
// `x_ref` 的生命周期不能长于 `x`
}

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();
// 引用的生命周期都不能长于 `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的:

struct Person {
name: &'static str,
}
fn main() {
let p = Person {
name: "fasterthanlime"
};
}

但是持有字符串(owned 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中) 如果要将非'static字符串存储在Person中,有以下两种方法: A. 使用生命周期泛型:

struct Person<'a> {
name: &'a str,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: &name };
// `p` 的生命周期不能长于 `name`
}

B. 获取字符串的所有权

struct Person {
name: String,
}
fn main() {
let name = format!("faterthan{}", "lime");
let p = Person { name: name };
// `name` 被移动到 `p`,它们的生命周期不再被限制
}

另外,在结构体字面量中,如果即将字段设置为同名的变量绑定时,可以简写:

let p = Person { name: name };
// 可悲简写为
let p = Person { name };

对于 Rust 中的许多类型,都有所有权(owned)和无所有权(non-owned)两种变体:

  • 字符串:String 是有所有权的,&str 是引用
  • 路径:PathBuf 是有所有权的,&Path 是引用
  • 集合:Vec<T> 是有所有权的,&[T] 是引用 在 Rust 中,所有权有以下规则:
  1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

切片#

切片(slice)是对多个连续元素的引用。 你可以引用一个向量的切片,例如:

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

上面的操作并不神奇。索引操作符foo[index]重载了IndexIndexMut特征。 ..语法只是范围(range)字面量,范围是标准库中定义的结构体。 切片区间可以是开放的,右边界可以是闭的,如果前面有=的话。

fn main() {
// 大于等于 0
println!("{:?}", (0..).contains(&100)); // true
// 严格小于 20
println!("{:?}", (..20).contains(&20)); // false
// 小于等于 0
println!("{:?}", (..=20).contains(&20)); // true
// 只包含 3, 4, 5
println!("{:?}", (3..6).contions(&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);
}

一样地,tail函数也可以这样写:

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);
}

这是因为向量是堆分配的(heap-allocated),并且它的生命周期不是'static &str值实际上是切片。

fn file_ext(name: &str) -> Option<&str> {
// 这不会返回一个新的字符串
// 而是返回参数的一个切片
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).unwarp_or("")
// error: `name` does not live long enough
};
println!("extension: {:?}", ext);
}

Result#

可能会出错的函数通常会返回Result

fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]);
println!("{:?}", s);
// 输出:Ok("🍉")
let s = std::str::from_utf8(&[195, 40]);
println!("{:?}", s);
// 输出:Err(Utf8Error { valid_up_to: 0, error_len: Some(1) })
}

如果你想在出错的时候panic,则可以使用.unwarp()

fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
println!("{:?}", s);
// 输出:"🍉"
let s = std::str::from_utf8(&[195, 40]).unwrap();
// 输出: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");
// 输出: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) => println!(e),
}
// 输出:🍉
}

或者使用if let

fn main() {
if let Ok(s) = std::std::from_utf8(&[240, 159, 141, 137]) {
println!("{}", s);
}
// 输出:🍉
}

或者你可以将错误提升出来:

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(())
}

*运算符可用于取消引用(dereference),但是你在访问字段和调用方法的时候并不需要用到:

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);
}
// 输出 `(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
}
// 现在 `Point` 是 `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); // 并且现在不会报错了
}

闭包#

闭包(closure)只是FnFnMutFnOnce类型的函数加上捕获传递的上下文。 它们的参数是被一对管道(|)包括,用逗号分隔的。它们不需要花括号,除非你需要多条语句。

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));
}
// 输出:
// 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));
// 我们的闭包引用了 `greeting`,所以其生命周期不能长于它
}

例如以下是无法工作的:

fn for_each_planet<F>(f: F)
where F: Fn(&'static str) + 'static // `F` 必须是 "'static" 生命周期
{
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` 不再被引用,它被移动到该闭包
}

FnMut需要被可变引用才能被调用,因此一次传参只能调用一次。 以下是可行的:

fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
foobar(|x| x * 2);
}
// 输出: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);
}
// 输出: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
});
}
// 输出: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` 被移动到我们的闭包中
// 并且我们的闭包通过返回将其移动到调用这中
// 记住 `String` 不是 `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);
// 使用了被移动的值:`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));
}
// 输出:
// tick 3...
// tick 2...
// tick 1...

这是马桶闭包:

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

之所以这么称呼是因为|_| ()看起来像马桶。

for in#

任何可迭代的东西都可以在for in循环中使用。 我们刚刚演示了在范围上使用,但实际上它也适用于向量:

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 thw number {}", i);
}
}
// 输出:
// I like the number 52
// I like the number 49
// I like the number 21

或者是一个实际的迭代器(iterator):

fn main() {
// 注意:`&str` 还有个 `.bytes()` 迭代器
// Rust 的 `char` 类型是一个 Unicode 标量值
for c in "rust".chars() {
println!("Give me a {}", c);
}
}
// 输出:
// Give me a r
// Give me a u
// Give me a s
// Give me a t

即使迭代器的成员被过滤(filter),映射(map)和展平(flat):

fn main() {
for c in "SuRPRISE INbOUND"
.chars()
.filter(|c| c.is_lowercase())
.flat_map(|c| c.to_uppercase())
{
print!("{}", c);
}
println!();
}
// 输出:UB

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

fn make_tester(answer: String) -> impl Fn(&str) -> bool {
move |challenge| {
challenge == answer
}
}
fn main() {
// 你可以使用 `.into()` 在类型变种间实施转换
// 例如 `&'static str` 和 `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"));
}
// 输出:
// false
// true

以及,省写生命周期:

fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ {
move |challenge| {
challenge == answer
}
}

到此,本文就结束了,现在你应该能够阅读他人的 Rust 代码。如果你想更进一步的学习,那么你可以查看:

If you didn’t code, you didn’t learn.

半小时速通 Rust
https://fuwari.vercel.app/posts/a-half-hour-to-learn-rust/
作者
Sugar Breeze
发布于
2024-02-17
许可协议
CC BY-NC-SA 4.0