pull/417/head
sunface 3 years ago
commit c776df2c4b

@ -255,7 +255,7 @@ fn main() {
## 自定义错误类型
虽然标准库定义了大量的错误类型,但是一个严谨的项目,光使用这些错误类型往往是不够的,例如我们可能会为暴露给用户的错误定义相应的类型。
为了帮助我们更好的定义错误Rust 在标准库中提供了一些可复用的特征,例如 `std::error::Erro` 特征:
为了帮助我们更好的定义错误Rust 在标准库中提供了一些可复用的特征,例如 `std::error::Error` 特征:
```rust
use std::fmt::{Debug, Display};
@ -599,7 +599,7 @@ enum MyError {
}
```
如上所示,只要简单谢谢注释,就可以实现错误处理了,惊不惊喜?
如上所示,只要简单写写注释,就可以实现错误处理了,惊不惊喜?
#### error-chain
[`error-chain`](https://github.com/rust-lang-deprecated/error-chain) 也是简单好用的库,可惜不再维护了,但是我觉得它依然可以在合适的地方大放光彩,值得大家去了解下。

@ -1,6 +1,6 @@
# 闭包closure
闭包这个词语由来已久自上世纪60年代就由 `Scheme` 语言引进之后被广泛用于函数式编程语言中进入21世纪后各种现代化的编程语言也都不约而同地把闭包作为核心特性纳入到语言设计中来。那么到底何为闭包
闭包这个词语由来已久,自上世纪 60 年代就由 `Scheme` 语言引进之后,被广泛用于函数式编程语言中,进入 21 世纪后,各种现代化的编程语言也都不约而同地把闭包作为核心特性纳入到语言设计中来。那么到底何为闭包?
闭包是**一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值**,例如:
```rust
@ -12,7 +12,7 @@ fn main() {
}
```
上面的代码展示了非常简单的闭包 `sum`,它拥有一个入参 `y`,同时捕获了作用域中的 `x` 的值,因此调用 `sum(2)` 意味着将 2(参数 `y`) 跟 1`x`)进行相加,最终返回它们的和:`3`。
上面的代码展示了非常简单的闭包 `sum`,它拥有一个入参 `y`,同时捕获了作用域中的 `x` 的值,因此调用 `sum(2)` 意味着将 2(参数 `y`跟 1`x`)进行相加,最终返回它们的和:`3`。
可以看到 `sum` 非常符合闭包的定义:可以赋值给变量,允许捕获调用者作用域中的值。
@ -41,15 +41,13 @@ fn workout(intensity: u32, random_number: u32) {
"旁边有妹子在看俯卧撑太low再来 {} 组卧推!",
muuuuu(intensity)
);
} else if random_number == 3 {
println!("昨天练过度了,今天还是休息下吧!");
} else {
if random_number == 3 {
println!("昨天练过度了,今天还是休息下吧!");
} else {
println!(
"昨天练过度了,今天干干有氧,跑步 {} 分钟!",
muuuuu(intensity)
);
}
println!(
"昨天练过度了,今天干干有氧,跑步 {} 分钟!",
muuuuu(intensity)
);
}
}
@ -64,11 +62,21 @@ fn main() {
}
```
可以看到,在健身时我们根据想要的强度来调整具体的动作,然后调用 `muuuuu` 函数来开始健身。这个程序本身很简单,没啥好说的,但是假如未来不用 `muuuu` 函数了,是不是得把所有 `muuuu` 都替换成,比如说 `woooo` ?如果 `muuuu` 出现了几十次,那意味着我们要修改几十处地方。
可以看到,在健身时我们根据想要的强度来调整具体的动作,然后调用 `muuuuu` 函数来开始健身。这个程序本身很简单,没啥好说的,但是假如未来不用 `muuuuu` 函数了,是不是得把所有 `muuuuu` 都替换成,比如说 `woooo` ?如果 `muuuuu` 出现了几十次,那意味着我们要修改几十处地方。
#### 函数变量实现
一个可行的办法是,把函数赋值给一个变量,然后通过变量调用:
```rust
use std::thread;
use std::time::Duration;
// 开始健身好累我得发出声音muuuu...
fn muuuuu(intensity: u32) -> u32 {
println!("muuuu.....");
thread::sleep(Duration::from_secs(2));
intensity
}
fn workout(intensity: u32, random_number: u32) {
let action = muuuuu;
if intensity < 25 {
@ -80,17 +88,25 @@ fn workout(intensity: u32, random_number: u32) {
"旁边有妹子在看俯卧撑太low, 再来 {} 组卧推!",
action(intensity)
);
} else if random_number == 3 {
println!("昨天练过度了,今天还是休息下吧!");
} else {
if random_number == 3 {
println!("昨天练过度了,今天还是休息下吧!");
} else {
println!(
"昨天练过度了,今天干干有氧, 跑步 {} 分钟!",
action(intensity)
);
}
println!(
"昨天练过度了,今天干干有氧, 跑步 {} 分钟!",
action(intensity)
);
}
}
fn main() {
// 强度
let intensity = 10;
// 随机值用来决定某个选择
let random_number = 7;
// 开始健身
workout(intensity, random_number);
}
```
@ -104,6 +120,9 @@ fn workout(intensity: u32, random_number: u32) {
上面提到 `intensity` 要是变化怎么办,简单,使用闭包来捕获它,这是我们的拿手好戏:
```rust
use std::thread;
use std::time::Duration;
fn workout(intensity: u32, random_number: u32) {
let action = || {
println!("muuuu.....");
@ -120,15 +139,13 @@ fn workout(intensity: u32, random_number: u32) {
"旁边有妹子在看俯卧撑太low再来 {} 组卧推!",
action()
);
} else if random_number == 3 {
println!("昨天练过度了,今天还是休息下吧!");
} else {
if random_number == 3 {
println!("昨天练过度了,今天还是休息下吧!");
} else {
println!(
"昨天练过度了,今天干干有氧,跑步 {} 分钟!",
action()
);
}
println!(
"昨天练过度了,今天干干有氧,跑步 {} 分钟!",
action()
);
}
}
@ -167,7 +184,7 @@ Rust 闭包在形式上借鉴了 `Smalltalk` 和 `Ruby` 语言,与函数最大
## 闭包的类型推导
Rust 是静态语言,因此所有的变量都具有类型,但是得益于编译器的强大类型推导能力,在很多时候我们并不需要显式地去声明类型,但是显然函数并不在此列,必须手动为函数的所有参数和返回值指定类型,原因在于函数往往会作为 API 提供给你的用户,因此你的用户必须在使用时知道传入参数的类型和返回值类型。
与函数相反闭包并不会作为API对外提供因此它可以享受编译器的类型推导能力无需标注参数和返回值的类型。
与函数相反,闭包并不会作为 API 对外提供,因此它可以享受编译器的类型推导能力,无需标注参数和返回值的类型。
为了增加代码可读性,有时候我们会显式地给类型进行标注,出于同样的目的,也可以给闭包标注类型:
```rust
@ -176,10 +193,10 @@ let sum = |x: i32, y: i32| -> i32 {
}
```
与之相比,不标注类型的闭包声明会更简洁些:`let sum = |x, y| x + y`,需要注意的是,针对 `sum` 闭包,如果你只进行了声明,但是没有使用,编译器会提示你为 `x,y` 添加类型标注,因为它缺乏必要的上下文:
与之相比,不标注类型的闭包声明会更简洁些:`let sum = |x, y| x + y`,需要注意的是,针对 `sum` 闭包,如果你只进行了声明,但是没有使用,编译器会提示你为 `x, y` 添加类型标注,因为它缺乏必要的上下文:
```rust
let sum = |x, y| x + y;
let v = sum(1,2);
let v = sum(1, 2);
```
这里我们使用了 `sum`,同时把 `1` 传给了 `x``2` 传给了 `y`,因此编译器才可以推导出 `x,y` 的类型为 `i32`
@ -274,7 +291,7 @@ where
## 捕获作用域中的值
在之前代码中,我们一直在用闭包的匿名函数特性(赋值给变量),然而闭包还拥有一项函数所不具备的特性:捕获作用域中的值。
在之前代码中,我们一直在用闭包的匿名函数特性(赋值给变量),然而闭包还拥有一项函数所不具备的特性:捕获作用域中的值。
```rust
fn main() {
let x = 4;

@ -1,6 +1,7 @@
# 函数式编程
罗马不是一天建成的编程语言亦是如此每一门编程语言在借鉴前辈的同时也会提出自己独有的特性Rust 即是如此。当站在巨人肩膀上时,一个人所能看到的就更高更远,恰好,我们看到了函数式语言的优秀特性,例如:
- 使用函数作为参数进行传递
- 使用函数作为函数返回值
- 将函数赋值给变量
@ -8,6 +9,7 @@
见猎心喜,我们忍不住就借鉴了过来,于是你能看到本章的内容,天下语言一大。。。跑题了。
关于函数式编程到底是什么的争论由来已久,本章节并不会踏足这个泥潭,因此我们在这里主要关注的是函数式特性:
- 闭包closure
- 迭代器iterator
- 模式匹配

@ -6,7 +6,7 @@
## For 循环与迭代器
从用途来看,迭代器跟 `for` 循环颇为相似,都是去遍历一个集合,但是实际上它们存在不小的差别,其中最主要的差别就是:**是否通过索引来访问集合**。
例如以下的JS代码就是一个循环
例如以下的 JS 代码就是一个循环:
```javascript
let arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
@ -26,7 +26,7 @@ for v in arr {
那又有同学要发问了,在 Rust 中数组是迭代器吗?因为在之前的代码中直接对数组 `arr` 进行了迭代,答案是 `No`。那既然数组不是迭代器,为啥咱可以对它的元素进行迭代呢?
简而言之就是数组实现了 `IntoIterator` 特征Rust 通过 `for` 语法糖,自动把实现了该特征的数组类型转换为迭代器(你也可以为自己的集合类型实现此特征),最终让我们可以直接对一个数组进行迭代,类似的还有:
简而言之就是数组实现了 `IntoIterator` 特征Rust 通过 `for` 语法糖,自动把实现了该特征的数组类型转换为迭代器(你也可以为自己的集合类型实现此特征),最终让我们可以直接对一个数组进行迭代,类似的还有:
```rust
for i in 1..10 {
println!("{}", i);
@ -45,7 +45,7 @@ for v in arr.into_iter() {
迭代器是函数语言的核心特性,它赋予了 Rust 远超于循环的强大表达能力,我们将在本章中一一为大家进行展现。
## 惰性初始化
在Rust中迭代器是惰性的意味着如果你不使用它那么它将不会发生任何事
Rust 中,迭代器是惰性的,意味着如果你不使用它,那么它将不会发生任何事:
```rust
let v1 = vec![1, 2, 3];
@ -95,7 +95,7 @@ fn main() {
- `next` 方法返回的是 `Option` 类型,当有值时返回 `Some(i32)`,无值时返回 `None`
- 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了 `Some(1)``Some(2)``Some(3)`
- 手动迭代必须将迭代器声明为 `mut` 可变,因为调用 `next` 会改变迭代器其中的状态数据(当前遍历的位置等),而 `for` 循环去迭代则无需标注 `mut`,因为它会帮我们自动完成
- 手动迭代必须将迭代器声明为 `mut` 可变,因为调用 `next` 会改变迭代器其中的状态数据(当前遍历的位置等),而 `for` 循环去迭代则无需标注 `mut`,因为它会帮我们自动完成
总之,`next` 方法对**迭代器的遍历是消耗性的**,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 `None`
@ -254,7 +254,7 @@ warning: unused `Map` that must be used
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: iterators are lazy and do nothing unless consumed // 迭代器map是惰性的这里不产生任何效果
= note: iterators are lazy and do nothing unless consumed // 迭代器 map 是惰性的,这里不产生任何效果
```
如上述中文注释所说,这里的 `map` 方法是一个迭代者适配器,它是惰性的,不产生任何行为,因此我们还需要一个消费者适配器进行收尾:
@ -305,7 +305,7 @@ fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
`filter` 是迭代器适配器,用于对迭代器中的每个值进行过滤。 它使用闭包作为参数,该闭包的参数 `s` 是来自迭代器中的值,然后使用 `s` 跟外部环境中的 `shoe_size` 进行比较,若相等,则在迭代器中保留 `s` 值,若不相等,则从迭代器中剔除 `s` 值,最终通过 `collect` 收集为 `Vec<Shoe>` 类型。
## 实现 Iterator 特征
之前的内容我们一直基于数组来创建迭代器,实际上,不仅仅是数组,基于其它集合类型一样可以创建迭代器,例如 `HashMap`。 你也可以创建自己的迭代器 - 只要为自定义类型实现 `Iterator` 特征即可。
之前的内容我们一直基于数组来创建迭代器,实际上,不仅仅是数组,基于其它集合类型一样可以创建迭代器,例如 `HashMap`。 你也可以创建自己的迭代器 —— 只要为自定义类型实现 `Iterator` 特征即可。
首先,创建一个计数器:
```rust
@ -464,14 +464,12 @@ test bench::bench_iter ... bench: 983,858 ns/iter (+/- 44,673)
迭代器是 Rust 的 **零成本抽象**zero-cost abstractions之一意味着抽象并不会引入运行时开销这与 `Bjarne Stroustrup`C++ 的设计和实现者)在 `Foundations of C++2012` 中所定义的 **零开销**zero-overhead如出一辙
```
In general, C++ implementations obey the zero-overhead principle: What you dont use, you dont pay for.
And further: What you do use, you couldnt hand code any better.
一般来说C++的实现遵循零开销原则:没有使用时,你不必为其买单。
更进一步说,需要使用时,你也无法写出更优的代码了。
(翻译一下:用就完事了)
```
> In general, C++ implementations obey the zero-overhead principle: What you dont use, you dont pay for.
> And further: What you do use, you couldnt hand code any better.
>
> 一般来说C++的实现遵循零开销原则:没有使用时,你不必为其买单。
> 更进一步说,需要使用时,你也无法写出更优的代码了。
> (翻译一下:用就完事了)
总之,迭代器是 Rust 受函数式语言启发而提供的高级语言特性可以写出更加简洁、逻辑清晰的代码。编译器还可以通过循环展开Unrolling、向量化、消除边界检查等优化手段使得迭代器和 `for` 循环都有极为高效的执行效率。

@ -13,7 +13,7 @@
举个例子,在之前的自引用章节中,我们就提到了相关的编译检查是很难绕过的,如果想要绕过,最常用的方法之一就是使用 [`unsafe` 和 `Pin`](https://course.rs/advance/circle-self-ref/self-referential.html)。
好在,当遇到这些情况时,我们可以使用 `unsafe` 来解决。此时,你需要替代编译器的部分职责对 `unsafe` 代码的正确性负责,例如在正常代码中不可能遇到的空指针解引用问题在 `unsafe` 中就可能会遇到,我们需要自己来处理好这些类似的问题。
好在,当遇到这些情况时,我们可以使用 `unsafe` 来解决。此时,你需要替代编译器的部分职责对 `unsafe` 代码的正确性负责,例如在正常代码中不可能遇到的空指针解引用问题在 `unsafe` 中就可能会遇到,我们需要自己来处理好这些类似的问题。
#### 特定任务的需要
@ -24,7 +24,7 @@ Rust 的一个主要定位就是系统编程,众所周知,系统编程就是
在了解了为何会有 `unsafe` 后,我们再来看看,除了这些必要性,`unsafe` 还能给我们带来哪些超能力。
## unsafe 的超能力
使用 `unsafe` 非常简单,只要将对应的代码块标记下即可:
使用 `unsafe` 非常简单,只要将对应的代码块标记下即可:
```rust
fn main() {
let mut num = 5;
@ -57,7 +57,7 @@ fn main() {
因此 `unsafe` 能给大家提供的也仅仅是之前的 5 种超能力在使用这5种能力时编译器才不会进行内存安全方面的检查最典型的就是使用**原生指针**(引用和原生指针有很大的区别)。
## 谈虎色变?
在网上充斥着这样的言论:千万不要使用 `unsafe因为它不安全甚至有些库会以没有` unsafe 代码作为噱头来吸引用户。事实上大可不必如果按照这个标准Rust 的标准库也将不复存在!
在网上充斥着这样的言论:`千万不要使用 unsafe因为它不安全`,甚至有些库会以没有 `unsafe` 代码作为噱头来吸引用户。事实上大可不必如果按照这个标准Rust 的标准库也将不复存在!
Rust 中的 `unsafe` 其实没有那么可怕,虽然听上去很不安全,但是实际上 Rust 依然提供了很多机制来帮我们提升了安全性,因此不必像对待 Go 语言的 `unsafe` 那样去畏惧于使用Rust中的 `unsafe`
@ -70,7 +70,7 @@ Rust 中的 `unsafe` 其实没有那么可怕,虽然听上去很不安全,
即使做到小心谨慎,依然会有出错的可能性,但是 `unsafe` 语句块决定了:就算内存访问出错了,你也能立刻意识到,错误是在 `unsafe` 代码块中,而不花大量时间像无头苍蝇一样去寻找问题所在。
正因为此,写代码时要尽量控制好 `unsafe` 的边界大小,越小的 `unsafe` 越会我们在未来感谢自己当初的选择。
正因为此,写代码时要尽量控制好 `unsafe` 的边界大小,越小的 `unsafe` 越会我们在未来感谢自己当初的选择。
除了控制边界大小,另一个很常用的方式就是在 `unsafe` 代码块外包裹一层 `safe` 的 API例如一个函数声明为 safe 的,然后在其内部有一块儿是 `unsafe` 代码。

@ -91,7 +91,7 @@ unsafe {
}
```
使用 `*` 可以对原生指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 `unsafe` 来包裹解引用的逻辑(切记,`unsafe` 语句块的范围一定要尽的小,具体原因在上一章节有讲)。
使用 `*` 可以对原生指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 `unsafe` 来包裹解引用的逻辑(切记,`unsafe` 语句块的范围一定要尽可能的小,具体原因在上一章节有讲)。
以上代码另一个值得注意的点就是:除了使用 `as` 来显式的转换,我们还使用了隐式的转换方式 `let c: *const i32 = &a;`。在实际使用中,我们建议使用 `as` 来转换,因为这种显式的方式更有助于提醒用户:你在使用的指针是原生指针,需要小心。
@ -145,7 +145,7 @@ unsafe {
## 用安全抽象包裹 unsafe 代码
一个函数包含了 `unsafe` 代码不代表我们需要将整个函数都定义为 `unsafe fn`。事实上,在标准库中有大量的安全函数,它们内部都包含了 `unsafe` 代码块,下面我们一起来看看一个很好用的标准库函数:`split_at_mut`。
大家可以想象一下这个场景:需要将一个数组分成两个切片,且每一个切片都要求可变的。类似需求在安全 Rust 中是很难实现的,因为要对同一个数组做两个可变借用:
大家可以想象一下这个场景:需要将一个数组分成两个切片,且每一个切片都要求可变的。类似需求在安全 Rust 中是很难实现的,因为要对同一个数组做两个可变借用:
```rust
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
@ -245,13 +245,13 @@ zsh: segmentation fault
前者相当不错,但是在很多时候,并没有那么多时间去重写,因此 `FFI` 就成了最佳选择。回到 Rust 语言上,由于这门语言依然很年轻,一些生态是缺失的,我们在写一些不是那么大众的项目时,可能会同时遇到没有相应的 Rust 库可用的尴尬境况,此时通过 `FFI` 去调用 C 语言的库就成了相当棒的选择。
还有在将 C/C++ 的代码重构为时,先将相关代码引入到 Rust 项目中,然后逐步重构,也是不错的(为什么用不错来形容?因为重构一个有一定规模的 C/C++ 项目远没有想象中美好,因此最好的选择还是对于新项目使用 Rust 实现,老项目。。就让它先运行着吧)。
还有在将 C/C++ 的代码重构为 Rust 时,先将相关代码引入到 Rust 项目中,然后逐步重构,也是不错的(为什么用不错来形容?因为重构一个有一定规模的 C/C++ 项目远没有想象中美好,因此最好的选择还是对于新项目使用 Rust 实现,老项目。。就让它先运行着吧)。
当然,除了 `FFI` 还有一个办法可以解决跨语言调用的问题那就是将其作为一个独立的服务然后使用网络调用的方式去访问HTTPgRPC都可以。
言归正传,之前我们提到 `unsafe` 的另一个重要目的就是对 `FFI` 提供支持,它的全称是 `Foreign Function Interface`,顾名思义,通过 `FFI` , 我们的 Rust 代码可以跟其它语言的外部代码进行交互。
下面的例子演示了如何调用 C 标准库中的 `abc` 函数:
下面的例子演示了如何调用 C 标准库中的 `abs` 函数:
```rust
extern "C" {
fn abs(input: i32) -> i32;
@ -267,7 +267,7 @@ fn main() {
C 语言的代码定义在了 `extern` 代码块中, 而 `extern` 必须使用 `unsafe` 才能进行进行调用,原因在于其它语言的代码并不会强制执行 Rust 的规则,因此 Rust 无法对这些代码进行检查,最终还是要靠开发者自己来保证代码的正确性和程序的安全性。
#### ABI
`exetrn "C"` 代码块中,我们列出了想要调用的外部函数的签名。其中 `"C"` 定义了外部函数所使用的**应用二进制接口**`ABI` (application binary interface)`ABI` 定义了如何在汇编层面来调用该函数。在所有 `ABI`C 语言的是最常见的。
`exetrn "C"` 代码块中,我们列出了想要调用的外部函数的签名。其中 `"C"` 定义了外部函数所使用的**应用二进制接口**`ABI` (Application Binary Interface)`ABI` 定义了如何在汇编层面来调用该函数。在所有 `ABI`C 语言的是最常见的。
#### 在其它语言中调用 Rust 函数
在 Rust 中调用其它语言的函数是让 Rust 利用其他语言的生态,那反过来可以吗?其他语言可以利用 Rust 的生态不?答案是肯定的。
@ -282,7 +282,7 @@ pub extern "C" fn call_from_c() {
上面的代码可以让 `call_from_c` 函数被 `C` 语言的代码调用,当然,前提是将其编译成一个共享库,然后链接到 C 语言中。
这里还有一个比较奇怪的注解 `#[no_mangle]`,它用于告诉 Rust 编译器不要乱改函数的名称。Mangling的定义是当Rust 因为编译需要去修改函数的名称,例如为了让名称包含更多的信息,这样其它的编译部分就能从该名称获取相应的信息,这种修改会导致函数名变得相当不可读。
这里还有一个比较奇怪的注解 `#[no_mangle]`,它用于告诉 Rust 编译器:不要乱改函数的名称。 `Mangling` 的定义是:当 Rust 因为编译需要去修改函数的名称,例如为了让名称包含更多的信息,这样其它的编译部分就能从该名称获取相应的信息,这种修改会导致函数名变得相当不可读。
因此,为了让 Rust 函数能顺利被其它语言调用,我们必须要禁止掉该功能。

Loading…
Cancel
Save