Merge pull request #415 from 1132719438/main

Some fixes in advance chapter
pull/417/head
孙飞Sunface 3 years ago committed by GitHub
commit f84b2db52e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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) 也是简单好用的库,可惜不再维护了,但是我觉得它依然可以在合适的地方大放光彩,值得大家去了解下。

@ -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