|
|
@ -63,7 +63,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 20-2: 创建指向任意内存地址的裸指针</span>
|
|
|
|
<span class="caption">示例 20-2: 创建指向任意内存地址的裸指针</span>
|
|
|
|
|
|
|
|
|
|
|
|
记得我们说过可以在安全代码中创建裸指针,不过不能 **解引用** 裸指针和读取其指向的数据。现在我们要做的就是对裸指针使用解引用运算符 `*`,这需要一个 `unsafe` 块,如示例 20-3 所示:
|
|
|
|
记得我们说过可以在安全代码中创建裸指针,但不能 **解引用** 裸指针和读取其指向的数据。示例 20-3 中,我们在裸指针上使用了解引用运算符 `*`,该操作需要一个 `unsafe` 块:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-03/src/main.rs:here}}
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-03/src/main.rs:here}}
|
|
|
@ -71,15 +71,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 20-3: 在 `unsafe` 块中解引用裸指针</span>
|
|
|
|
<span class="caption">示例 20-3: 在 `unsafe` 块中解引用裸指针</span>
|
|
|
|
|
|
|
|
|
|
|
|
创建一个指针不会造成任何危险;只有当访问其指向的值时才有可能遇到无效的值。
|
|
|
|
创建一个指针不会造成任何危害;只有当访问其指向的值时才有可能遇到无效的值。
|
|
|
|
|
|
|
|
|
|
|
|
还需注意示例 20-1 和 20-3 中创建了同时指向相同内存位置 `num` 的裸指针 `*const i32` 和 `*mut i32`。相反如果尝试同时创建 `num` 的不可变和可变引用,将无法通过编译,因为 Rust 的所有权规则不允许在拥有任何不可变引用的同时再创建一个可变引用。通过裸指针,就能够同时创建同一地址的可变指针和不可变指针,若通过可变指针修改数据,则可能潜在造成数据竞争。请多加小心!
|
|
|
|
还需注意示例 20-1 和 20-3 中创建了同时指向相同内存位置 `num` 的裸指针 `*const i32` 和 `*mut i32`。相反如果尝试同时创建 `num` 的不可变和可变引用,代码将无法通过编译,因为 Rust 的所有权规则不允许在拥有任何不可变引用的同时再创建可变引用。通过裸指针,就能够同时创建同一地址的可变指针和不可变指针,若通过可变指针修改数据,则可能潜在造成数据竞争。请多加小心!
|
|
|
|
|
|
|
|
|
|
|
|
既然存在这么多的危险,为何还要使用裸指针呢?一个主要的应用场景便是调用 C 代码接口,这在下一部分 [“调用不安全函数或方法”](#调用不安全函数或方法) 中会讲到。另一个场景是构建借用检查器无法理解的安全抽象。让我们先介绍不安全函数,接着看一看使用不安全代码的安全抽象的例子。
|
|
|
|
既然存在这么多的危险,为何还要使用裸指针呢?一个主要的应用场景便是调用 C 代码接口,这在下一部分 [“调用不安全函数或方法”](#调用不安全函数或方法) 中会讲到。另一个场景是构建借用检查器无法理解的安全抽象。让我们先介绍不安全函数,接着看一看使用不安全代码的安全抽象的示例。
|
|
|
|
|
|
|
|
|
|
|
|
### 调用不安全函数或方法
|
|
|
|
### 调用不安全函数或方法
|
|
|
|
|
|
|
|
|
|
|
|
第二类可以在不安全块中进行的操作是调用不安全函数。不安全函数和方法与常规函数方法十分类似,除了其开头有一个额外的 `unsafe`。在此上下文中,关键字`unsafe`表示该函数具有调用时需要满足的要求,而 Rust 不会保证满足这些要求。通过在 `unsafe` 块中调用不安全函数,表明我们已经阅读过此函数的文档并对其是否满足函数自身的契约负责。
|
|
|
|
第二类可以在不安全块中进行的操作是调用不安全函数。不安全函数和方法与常规函数方法十分类似,除了其开头有一个额外的 `unsafe`。在此上下文中,关键字 `unsafe` 表示该函数具有调用时需要满足的要求,而 Rust 不会保证满足这些要求。通过在 `unsafe` 块中调用不安全函数,表明我们已经阅读过此函数的文档并对其是否满足函数自身的契约负责。
|
|
|
|
|
|
|
|
|
|
|
|
如下是一个没有做任何操作的不安全函数 `dangerous` 的例子:
|
|
|
|
如下是一个没有做任何操作的不安全函数 `dangerous` 的例子:
|
|
|
|
|
|
|
|
|
|
|
@ -93,13 +93,15 @@
|
|
|
|
{{#include ../listings/ch20-advanced-features/output-only-01-missing-unsafe/output.txt}}
|
|
|
|
{{#include ../listings/ch20-advanced-features/output-only-01-missing-unsafe/output.txt}}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
通过 `unsafe` 块,我们向 Rust 保证了我们已经阅读过函数的文档,理解如何正确使用,并验证过其满足函数的契约。
|
|
|
|
通过 `unsafe` 块,我们向 Rust 断言我们已经阅读过函数的文档,理解如何正确使用它,并核实我们履行了该函数的契约。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在不安全函数的函数体内部执行不安全操作时,同样需要使用 `unsafe` 块,就像在普通函数中一样,如果忘记了编译器会发出警告。这有助于将 `unsafe` 块保持得尽可能小,因为 `unsafe` 操作并不一定需要覆盖整个函数体。
|
|
|
|
|
|
|
|
|
|
|
|
不安全函数体也是有效的 `unsafe` 块,所以在不安全函数中进行另一个不安全操作时无需新增额外的 `unsafe` 块。
|
|
|
|
不安全函数体也是有效的 `unsafe` 块,所以在不安全函数中进行另一个不安全操作时无需新增额外的 `unsafe` 块。
|
|
|
|
|
|
|
|
|
|
|
|
#### 创建不安全代码的安全抽象
|
|
|
|
#### 创建不安全代码的安全抽象
|
|
|
|
|
|
|
|
|
|
|
|
仅仅因为函数包含不安全代码并不意味着整个函数都需要标记为不安全的。事实上,将不安全代码封装进安全函数是一个常见的抽象。作为一个例子,了解一下标准库中的函数 `split_at_mut`,它需要一些不安全代码,让我们探索如何可以实现它。这个安全函数定义于可变 slice 之上:它获取一个 slice 并从给定的索引参数开始将其分为两个 slice。`split_at_mut` 的用法如示例 20-4 所示:
|
|
|
|
仅仅因为函数包含不安全代码并不意味着整个函数都需要标记为不安全的。事实上,将不安全代码封装进安全函数是一种常见的抽象方式。作为一个例子,了解一下标准库中的函数 `split_at_mut`,它需要一些不安全代码,让我们探索如何可以实现它。这个安全函数定义于可变 slice 之上:它获取一个 slice 并从给定的索引参数开始将其分割为两个 slice。示例 20-4 展示了如何使用 `split_at_mut`。
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-04/src/main.rs:here}}
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-04/src/main.rs:here}}
|
|
|
@ -125,7 +127,7 @@
|
|
|
|
{{#include ../listings/ch20-advanced-features/listing-20-05/output.txt}}
|
|
|
|
{{#include ../listings/ch20-advanced-features/listing-20-05/output.txt}}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能够理解这些。当我们知道某些事是可以的而 Rust 不知道的时候,就是触及不安全代码的时候了
|
|
|
|
Rust 的借用检查器无法理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。本质上借用 slice 的不同部分是可以的,因为这两段 slice 不会重叠,不过 Rust 还没有智能到能够理解这些。当我们知道某些事是可以的而 Rust 不知道的时候,就是触及不安全代码的时候了
|
|
|
|
|
|
|
|
|
|
|
|
示例 20-6 展示了如何使用 `unsafe` 块,裸指针和一些不安全函数调用来实现 `split_at_mut`:
|
|
|
|
示例 20-6 展示了如何使用 `unsafe` 块,裸指针和一些不安全函数调用来实现 `split_at_mut`:
|
|
|
|
|
|
|
|
|
|
|
@ -135,15 +137,15 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 20-6: 在 `split_at_mut` 函数的实现中使用不安全代码</span>
|
|
|
|
<span class="caption">示例 20-6: 在 `split_at_mut` 函数的实现中使用不安全代码</span>
|
|
|
|
|
|
|
|
|
|
|
|
回忆第四章的 [“Slice 类型” ][the-slice-type] 部分,slice 是一个指向一些数据的指针,并带有该 slice 的长度。可以使用 `len` 方法获取 slice 的长度,使用 `as_mut_ptr` 方法访问 slice 的裸指针。在这个例子中,因为有一个 `i32` 值的可变 slice,`as_mut_ptr` 返回一个 `*mut i32` 类型的裸指针,储存在 `ptr` 变量中。
|
|
|
|
回忆第四章的[“Slice 类型” ][the-slice-type]部分,slice 是一个指向一些数据的指针,并带有该 slice 的长度。可以使用 `len` 方法获取 slice 的长度,使用 `as_mut_ptr` 方法访问 slice 的裸指针。在这个例子中,因为有一个 `i32` 值的可变 slice,`as_mut_ptr` 返回一个 `*mut i32` 类型的裸指针,并将其存储在 `ptr` 变量中。
|
|
|
|
|
|
|
|
|
|
|
|
我们保持索引 `mid` 位于 slice 中的断言。接着是不安全代码:`slice::from_raw_parts_mut` 函数获取一个裸指针和一个长度来创建一个 slice。这里使用此函数从 `ptr` 中创建了一个有 `mid` 个项的 slice。之后在 `ptr` 上调用 `add` 方法并使用 `mid` 作为参数来获取一个从 `mid` 开始的裸指针,使用这个裸指针并以 `mid` 之后项的数量为长度创建一个 slice。
|
|
|
|
我们保持索引 `mid` 位于 slice 中的断言。接着是不安全代码:`slice::from_raw_parts_mut` 函数获取一个裸指针和一个长度来创建一个 slice。这里使用此函数从 `ptr` 中创建了一个有 `mid` 个项的 slice。之后在 `ptr` 上调用 `add` 方法并使用 `mid` 作为参数来获取一个从 `mid` 开始的裸指针,使用这个裸指针并以 `mid` 之后项的数量为长度创建另一个 slice。
|
|
|
|
|
|
|
|
|
|
|
|
`slice::from_raw_parts_mut` 函数是不安全的因为它获取一个裸指针,并必须确信这个指针是有效的。裸指针上的 `add` 方法也是不安全的,因为其必须确信此地址偏移量也是有效的指针。因此必须将 `slice::from_raw_parts_mut` 和 `add` 放入 `unsafe` 块中以便能调用它们。通过观察代码,和增加 `mid` 必然小于等于 `len` 的断言,我们可以说 `unsafe` 块中所有的裸指针将是有效的 slice 中数据的指针。这是一个可以接受的 `unsafe` 的恰当用法。
|
|
|
|
`slice::from_raw_parts_mut` 函数是不安全的因为它获取一个裸指针,并必须确信这个指针是有效的。裸指针上的 `add` 方法也是不安全的,因为其必须确信此地址偏移量也是有效的指针。因此必须将 `slice::from_raw_parts_mut` 和 `add` 放入 `unsafe` 块中以便能调用它们。通过观察代码,和增加 `mid` 必然小于等于 `len` 的断言,我们可以说 `unsafe` 块中所有的裸指针将是有效的 slice 中数据的指针。这是一个可以接受的 `unsafe` 的恰当用法。
|
|
|
|
|
|
|
|
|
|
|
|
注意无需将 `split_at_mut` 函数的结果标记为 `unsafe`,并可以在安全 Rust 中调用此函数。我们创建了一个不安全代码的安全抽象,其代码以一种安全的方式使用了 `unsafe` 代码,因为其只从这个函数访问的数据中创建了有效的指针。
|
|
|
|
注意无需将 `split_at_mut` 函数的结果标记为 `unsafe`,并可以在安全 Rust 中调用此函数。我们创建了一个不安全代码的安全抽象,其代码以一种安全的方式使用了 `unsafe` 代码,因为其只从这个函数访问的数据中创建了有效的指针。
|
|
|
|
|
|
|
|
|
|
|
|
与此相对,示例 20-7 中的 `slice::from_raw_parts_mut` 在使用 slice 时很有可能会崩溃。这段代码获取任意内存地址并创建了一个长为一万的 slice:
|
|
|
|
与此相对,示例 20-7 中的 `slice::from_raw_parts_mut` 在使用 slice 时很有可能会崩溃。这段代码获取任意内存地址并创建了一个长度为一万的 slice:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-07/src/main.rs:here}}
|
|
|
|
{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-07/src/main.rs:here}}
|
|
|
|