|
|
@ -1,10 +1,10 @@
|
|
|
|
## 不安全 Rust
|
|
|
|
## 不安全 Rust
|
|
|
|
|
|
|
|
|
|
|
|
> [ch19-01-unsafe-rust.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch19-01-unsafe-rust.md)
|
|
|
|
> [ch19-01-unsafe-rust.md](https://github.com/rust-lang/book/blob/master/src/ch19-01-unsafe-rust.md)
|
|
|
|
> <br>
|
|
|
|
> <br>
|
|
|
|
> commit c2b43bd978a9176ac9aba22595e33d2335b2d04b
|
|
|
|
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
|
|
|
|
|
|
|
|
|
|
|
目前为止讨论过的代码都有 Rust 在编译时会强制执行的内存安全保证。然而,Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:不安全 Rust。它与常规 Rust 代码无异,但是会提供额外的超级力量。
|
|
|
|
目前为止讨论过的代码都有 Rust 在编译时会强制执行的内存安全保证。然而,Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:这被称为 **不安全 Rust**(*unsafe Rust*)。它与常规 Rust 代码无异,但是会提供额外的超级力量。
|
|
|
|
|
|
|
|
|
|
|
|
不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,拒绝一些有效的程序比接受无效程序要好一些。这必然意味着有时代码可能是合法的,但是 Rust 不这么认为!在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。”这么做的缺点就是你只能靠自己了:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。
|
|
|
|
不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,拒绝一些有效的程序比接受无效程序要好一些。这必然意味着有时代码可能是合法的,但是 Rust 不这么认为!在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。”这么做的缺点就是你只能靠自己了:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。
|
|
|
|
|
|
|
|
|
|
|
@ -12,14 +12,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
### 不安全的超级力量
|
|
|
|
### 不安全的超级力量
|
|
|
|
|
|
|
|
|
|
|
|
可以通过 `unsafe` 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有四类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作。称之为 “不安全的超级力量。”这些超级力量是:
|
|
|
|
可以通过 `unsafe` 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有四类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,它们称之为 “不安全的超级力量。” 这些超级力量是:
|
|
|
|
|
|
|
|
|
|
|
|
1. 解引用裸指针
|
|
|
|
* 解引用裸指针
|
|
|
|
2. 调用不安全的函数或方法
|
|
|
|
* 调用不安全的函数或方法
|
|
|
|
3. 访问或修改可变静态变量
|
|
|
|
* 访问或修改可变静态变量
|
|
|
|
4. 实现不安全 trait
|
|
|
|
* 实现不安全 trait
|
|
|
|
|
|
|
|
|
|
|
|
有一点很重要,`unsafe` 并不会关闭借用检查器或禁用任何其他 Rust 安全检查:如果在不安全代码中使用引用,其仍会被检查。`unsafe` 关键字只是提供了那四个不会被编译器检查内存安全的功能。你仍然能在不安全块中获得某种程度的安全!
|
|
|
|
有一点很重要,`unsafe` 并不会关闭借用检查器或禁用任何其他 Rust 安全检查:如果在不安全代码中使用引用,其仍会被检查。`unsafe` 关键字只是提供了那四个不会被编译器检查内存安全的功能。你仍然能在不安全块中获得某种程度的安全。
|
|
|
|
|
|
|
|
|
|
|
|
再者,`unsafe` 不意味着块中的代码就一定是危险的或者必然导致内存安全问题:其意图在于作为程序员你将会确保 `unsafe` 块中的代码以有效的方式访问内存。
|
|
|
|
再者,`unsafe` 不意味着块中的代码就一定是危险的或者必然导致内存安全问题:其意图在于作为程序员你将会确保 `unsafe` 块中的代码以有效的方式访问内存。
|
|
|
|
|
|
|
|
|
|
|
@ -31,24 +31,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
### 解引用裸指针
|
|
|
|
### 解引用裸指针
|
|
|
|
|
|
|
|
|
|
|
|
回到第四章的 “悬垂引用” 部分,那里提到了编译器会确保引用总是有效的。不安全 Rust 有两个被称为 **裸指针**(*raw pointers*)的类似于引用的新类型。和引用一样,裸指针是可变或不可变的,分别写作 `*const T` 和 `*mut T`。这里的星号不是解引用运算符;它是类型名称的一部分。在裸指针的上下文中,“裸指针” 意味着指针解引用之后不能直接赋值。
|
|
|
|
回到第四章的 “悬垂引用” 部分,那里提到了编译器会确保引用总是有效的。不安全 Rust 有两个被称为 **裸指针**(*raw pointers*)的类似于引用的新类型。和引用一样,裸指针是可变或不可变的,分别写作 `*const T` 和 `*mut T`。这里的星号不是解引用运算符;它是类型名称的一部分。在裸指针的上下文中,**不可变** 意味着指针解引用之后不能直接赋值。
|
|
|
|
|
|
|
|
|
|
|
|
与引用和智能指针的区别在于,记住裸指针
|
|
|
|
与引用和智能指针的区别在于,记住裸指针
|
|
|
|
|
|
|
|
|
|
|
|
- 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
|
|
|
|
* 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
|
|
|
|
- 不保证指向有效的内存
|
|
|
|
* 不保证指向有效的内存
|
|
|
|
- 允许为空
|
|
|
|
* 允许为空
|
|
|
|
- 不能实现任何自动清理功能
|
|
|
|
* 不能实现任何自动清理功能
|
|
|
|
|
|
|
|
|
|
|
|
通过去掉 Rust 强加的保证,你可以放弃安全保证以换取性能或使用另一个语言或硬件接口的能力,此时 Rust 的保证并不适用。
|
|
|
|
通过去掉 Rust 强加的保证,你可以放弃安全保证以换取性能或使用另一个语言或硬件接口的能力,此时 Rust 的保证并不适用。
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Can you say here what benefits these provide, over smart pointers and
|
|
|
|
|
|
|
|
references, and using the aspects in these bullets? -->
|
|
|
|
|
|
|
|
<!-- There aren't really benefits to each of these individually. These are the
|
|
|
|
|
|
|
|
caveats that the reader needs to be aware of when working with raw pointers.
|
|
|
|
|
|
|
|
You'd choose to use raw pointers to do something that you can't do with smart
|
|
|
|
|
|
|
|
pointers or references. I've tried to clarify above /Carol -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
示例 19-1 展示了如何从引用同时创建不可变和可变裸指针。
|
|
|
|
示例 19-1 展示了如何从引用同时创建不可变和可变裸指针。
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
@ -60,16 +53,11 @@ let r2 = &mut num as *mut i32;
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-1: 通过引用创建裸指针</span>
|
|
|
|
<span class="caption">示例 19-1: 通过引用创建裸指针</span>
|
|
|
|
|
|
|
|
|
|
|
|
<!--So we create a raw pointer using the dereference operator? Is that the same
|
|
|
|
注意这里没有引入 `unsafe` 关键字。可以在安全代码中 **创建** 裸指针,只是不能在不安全块之外 **解引用** 裸指针,稍后便会看到。
|
|
|
|
operator? Is it worth touching on why? -->
|
|
|
|
|
|
|
|
<!-- It's not the dereference operator, the * is part of the type. Tried to
|
|
|
|
|
|
|
|
clarify above where the types are introduced /Carol -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
注意这里没有引入 `unsafe` 关键字 ———— 可以在安全代码中 **创建** 裸指针,只是不能在不安全块之外 **解引用** 裸指针,稍后便会看到。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这里使用 `as` 将不可变和可变引用强转为对应的裸指针类型。因为直接从保证安全的引用来创建他们,可以知道这些特定的裸指针是有效,但是不能对任何裸指针做出如此假设。
|
|
|
|
这里使用 `as` 将不可变和可变引用强转为对应的裸指针类型。因为直接从保证安全的引用来创建他们,可以知道这些特定的裸指针是有效,但是不能对任何裸指针做出如此假设。
|
|
|
|
|
|
|
|
|
|
|
|
接下来会创建一个不能确定其有效性的裸指针,示例 19-2 展示了如何创建一个指向任意内存地址的裸指针。尝试使用任意内存是未定义行为:此地址可能有数据也可能没有,编译器可能会优化掉这个内存访问,或者程序可能会出现段错误(segfault)。通常没有好的理由编写这样的代码,不过却是可行的:
|
|
|
|
接下来会创建一个不能确定其有效性的裸指针,示例 19-2 展示了如何创建一个指向任意内存地址的裸指针。尝试使用任意内存是未定义行为:此地址可能有数据也可能没有,编译器可能会优化掉这个内存访问,或者程序可能会出现段错误(segmentation fault)。通常没有好的理由编写这样的代码,不过却是可行的:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
let address = 0x012345usize;
|
|
|
|
let address = 0x012345usize;
|
|
|
@ -78,9 +66,9 @@ let r = address as *const i32;
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-2: 创建指向任意内存地址的裸指针</span>
|
|
|
|
<span class="caption">示例 19-2: 创建指向任意内存地址的裸指针</span>
|
|
|
|
|
|
|
|
|
|
|
|
记得我们说过可以在安全代码中创建裸指针,不过不能 **解引用** 裸指针和读取其指向的数据。现在我们要做的就是对裸指针使用解引用运算符 `*`,只要求一个 `unsafe` 块,如示例 19-3 所示:
|
|
|
|
记得我们说过可以在安全代码中创建裸指针,不过不能 **解引用** 裸指针和读取其指向的数据。现在我们要做的就是对裸指针使用解引用运算符 `*`,这需要一个 `unsafe` 块,如示例 19-3 所示:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,unsafe
|
|
|
|
let mut num = 5;
|
|
|
|
let mut num = 5;
|
|
|
|
|
|
|
|
|
|
|
|
let r1 = &num as *const i32;
|
|
|
|
let r1 = &num as *const i32;
|
|
|
@ -98,19 +86,15 @@ unsafe {
|
|
|
|
|
|
|
|
|
|
|
|
还需注意示例 19-1 和 19-3 中创建了同时指向相同内存位置 `num` 的裸指针 `*const i32` 和 `*mut i32`。相反如果尝试创建 `num` 的不可变和可变引用,这将无法编译因为 Rust 的所有权规则不允许拥有可变引用的同时拥有不可变引用。通过裸指针,就能够同时创建同一地址的可变指针和不可变指针,若通过可变指针修改数据,则可能潜在造成数据竞争。请多加小心!
|
|
|
|
还需注意示例 19-1 和 19-3 中创建了同时指向相同内存位置 `num` 的裸指针 `*const i32` 和 `*mut i32`。相反如果尝试创建 `num` 的不可变和可变引用,这将无法编译因为 Rust 的所有权规则不允许拥有可变引用的同时拥有不可变引用。通过裸指针,就能够同时创建同一地址的可变指针和不可变指针,若通过可变指针修改数据,则可能潜在造成数据竞争。请多加小心!
|
|
|
|
|
|
|
|
|
|
|
|
既然存在这么多的危险,为何还要使用裸指针呢?一个主要的应用场景便是调用 C 代码接口,这在下一部分不安全函数中会讲到。另一个场景是构建借用检查器无法理解的安全抽象。让我们先介绍不安全函数,接着看一看使用不安全代码的安全抽象的例子。
|
|
|
|
既然存在这么多的危险,为何还要使用裸指针呢?一个主要的应用场景便是调用 C 代码接口,这在下一部分 “调用不安全函数或方法” 中会讲到。另一个场景是构建借用检查器无法理解的安全抽象。让我们先介绍不安全函数,接着看一看使用不安全代码的安全抽象的例子。
|
|
|
|
|
|
|
|
|
|
|
|
### 调用不安全函数或方法
|
|
|
|
### 调用不安全函数或方法
|
|
|
|
|
|
|
|
|
|
|
|
第二类要求使用不安全块的操作是调用不安全函数。不安全函数和方法与常规函数方法十分类似,除了其开头有一个额外的 `unsafe`。`unsafe` 表明我们作为程序需要满足其要求,因为 Rust 不会保证满足这些要求。通过在 `unsafe` 块中调用不安全函数,我们表明已经阅读过此函数的文档并对其是否满足函数自身的契约负责。
|
|
|
|
第二类要求使用不安全块的操作是调用不安全函数。不安全函数和方法与常规函数方法十分类似,除了其开头有一个额外的 `unsafe`。`unsafe` 表明我们作为程序需要满足其要求,因为 Rust 不会保证满足这些要求。通过在 `unsafe` 块中调用不安全函数,我们表明已经阅读过此函数的文档并对其是否满足函数自身的契约负责。
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Above -- so what is the difference, when and why would we ever use the
|
|
|
|
|
|
|
|
unsafe function? -->
|
|
|
|
|
|
|
|
<!-- Tried to clarify /Carol -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
如下是一个没有做任何操作的不安全函数 `dangerous` 的例子:
|
|
|
|
如下是一个没有做任何操作的不安全函数 `dangerous` 的例子:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,unsafe
|
|
|
|
unsafe fn dangerous() {}
|
|
|
|
unsafe fn dangerous() {}
|
|
|
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
unsafe {
|
|
|
@ -128,7 +112,7 @@ error[E0133]: call to unsafe function requires unsafe function or block
|
|
|
|
| ^^^^^^^^^^^ call to unsafe function
|
|
|
|
| ^^^^^^^^^^^ call to unsafe function
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
通过将 `dangerous` 调用插入 `unsafe` 块中,我们就向 Rust 保证了我们已经阅读过函数的文档,理解如何正确,并验证过所有内容的正确性。
|
|
|
|
通过将 `dangerous` 调用插入 `unsafe` 块中,我们就向 Rust 保证了我们已经阅读过函数的文档,理解如何正确使用,并验证过其满足函数的契约。
|
|
|
|
|
|
|
|
|
|
|
|
不安全函数体也是有效的 `unsafe` 块,所以在不安全函数中进行另一个不安全操作时无需新增额外的 `unsafe` 块。
|
|
|
|
不安全函数体也是有效的 `unsafe` 块,所以在不安全函数中进行另一个不安全操作时无需新增额外的 `unsafe` 块。
|
|
|
|
|
|
|
|
|
|
|
@ -149,11 +133,9 @@ assert_eq!(b, &mut [4, 5, 6]);
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-4: 使用安全的 `split_at_mut` 函数</span>
|
|
|
|
<span class="caption">示例 19-4: 使用安全的 `split_at_mut` 函数</span>
|
|
|
|
|
|
|
|
|
|
|
|
这个函数无法只通过安全 Rust 实现。一个尝试可能看起来像示例 19-5,它不能编译。处于简单考虑,我们将 `split_at_mut` 实现为函数而不是方法,并只处理 `i32` 值而非泛型 `T` 的 slice。
|
|
|
|
这个函数无法只通过安全 Rust 实现。一个尝试可能看起来像示例 19-5,它不能编译。出于简单考虑,我们将 `split_at_mut` 实现为函数而不是方法,并只处理 `i32` 值而非泛型 `T` 的 slice。
|
|
|
|
|
|
|
|
|
|
|
|
用安全的Rust代码是不能实现这个函数的. 如果要试一下用安全的Rust来实现它可以参考例19-5. 简单起见, 我们把`split_at_mut`实现成一个函数而不是一个方法, 这个函数只处理`i32`类型的切片而不是泛型类型`T`的切片:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
```rust,ignore,does_not_compile
|
|
|
|
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
|
|
|
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
|
|
|
let len = slice.len();
|
|
|
|
let len = slice.len();
|
|
|
|
|
|
|
|
|
|
|
@ -166,11 +148,11 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-5: 尝试只使用安全 Rust 来实现 `split_at_mut`</span>
|
|
|
|
<span class="caption">示例 19-5: 尝试只使用安全 Rust 来实现 `split_at_mut`</span>
|
|
|
|
|
|
|
|
|
|
|
|
此函数有限获取 slice 的长度,然后通过检查参数是否小于或等于这个长度来断言参数所给定的索引位于 slice 当中。该断言意味着如果传入的索引比要分割的 slice 的索引更大,此函数在尝试使用这个索引前 panic。
|
|
|
|
此函数首先获取 slice 的长度,然后通过检查参数是否小于或等于这个长度来断言参数所给定的索引位于 slice 当中。该断言意味着如果传入的索引比要分割的 slice 的索引更大,此函数在尝试使用这个索引前 panic。
|
|
|
|
|
|
|
|
|
|
|
|
次后我们在一个元组中返回两个可变的 slice:一个从原始 slice 的开头直到 `mid` 索引,另一个从 `mid` 直到原 slice 的结尾。
|
|
|
|
次后我们在一个元组中返回两个可变的 slice:一个从原始 slice 的开头直到 `mid` 索引,另一个从 `mid` 直到原 slice 的结尾。
|
|
|
|
|
|
|
|
|
|
|
|
如果尝试编译此代码,会得到一个错误:
|
|
|
|
如果尝试编译示例 19-5 的代码,会得到一个错误:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
```text
|
|
|
|
error[E0499]: cannot borrow `*slice` as mutable more than once at a time
|
|
|
|
error[E0499]: cannot borrow `*slice` as mutable more than once at a time
|
|
|
@ -184,11 +166,11 @@ error[E0499]: cannot borrow `*slice` as mutable more than once at a time
|
|
|
|
| - first borrow ends here
|
|
|
|
| - first borrow ends here
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。本质上借用 slice 的不同部分是可以的,因为这样两个 slice 不会重叠,不过 Rust 还没有智能到理解这些。当我们知道某些事是可以的而 Rust 不知道的时候,就是触及不安全代码的时候了
|
|
|
|
Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能够理解这些。当我们知道某些事是可以的而 Rust 不知道的时候,就是触及不安全代码的时候了
|
|
|
|
|
|
|
|
|
|
|
|
示例 19-6 展示了如何使用 `unsafe` 块,裸指针和一些不安全函数调用来实现 `split_at_mut`:
|
|
|
|
示例 19-6 展示了如何使用 `unsafe` 块,裸指针和一些不安全函数调用来实现 `split_at_mut`:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,unsafe
|
|
|
|
use std::slice;
|
|
|
|
use std::slice;
|
|
|
|
|
|
|
|
|
|
|
|
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
|
|
|
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
|
|
@ -206,7 +188,7 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-6: 在 `split_at_mut` 函数的实现中使用不安全代码</span>
|
|
|
|
<span class="caption">示例 19-6: 在 `split_at_mut` 函数的实现中使用不安全代码</span>
|
|
|
|
|
|
|
|
|
|
|
|
回忆第四章的 “Slice” 部分,slice 是一个指向一些数据的指针,并带有该 slice 的长度。可以使用 `len` 方法获取 slice 的长度,使用 `as_mut_ptr` 方法访问 slice 的裸指针。在这个例子中,因为有一个 `i32` 值的可变 slice,`as_mut_ptr` 返回一个 `*mut i32` 类型的裸指针,储存在 `ptr` 变量中。
|
|
|
|
回忆第四章的 “Slice 类型” 部分,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` 上调用 `offset` 方法并使用 `mid` 作为参数来获取一个从 `mid` 开始的裸指针,使用这个裸指针并以 `mid` 之后项的数量为长度创建一个 slice。
|
|
|
|
我们保持索引 `mid` 位于 slice 中的断言。接着是不安全代码:`slice::from_raw_parts_mut` 函数获取一个裸指针和一个长度来创建一个 slice。这里使用此函数从 `ptr` 中创建了一个有 `mid` 个项的 slice。之后在 `ptr` 上调用 `offset` 方法并使用 `mid` 作为参数来获取一个从 `mid` 开始的裸指针,使用这个裸指针并以 `mid` 之后项的数量为长度创建一个 slice。
|
|
|
|
|
|
|
|
|
|
|
@ -216,33 +198,30 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
|
|
|
|
|
|
|
|
|
|
|
与此相对,示例 19-7 中的 `slice::from_raw_parts_mut` 在使用 slice 时很有可能会崩溃。这段代码获取任意内存地址并创建了一个长为一万的 slice:
|
|
|
|
与此相对,示例 19-7 中的 `slice::from_raw_parts_mut` 在使用 slice 时很有可能会崩溃。这段代码获取任意内存地址并创建了一个长为一万的 slice:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,unsafe
|
|
|
|
use std::slice;
|
|
|
|
use std::slice;
|
|
|
|
|
|
|
|
|
|
|
|
let address = 0x012345usize;
|
|
|
|
let address = 0x01234usize;
|
|
|
|
let r = address as *mut i32;
|
|
|
|
let r = address as *mut i32;
|
|
|
|
|
|
|
|
|
|
|
|
let slice = unsafe {
|
|
|
|
let slice : &[i32] = unsafe {
|
|
|
|
slice::from_raw_parts_mut(r, 10000)
|
|
|
|
slice::from_raw_parts_mut(r, 10000)
|
|
|
|
};
|
|
|
|
};
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 19-7: 通过任意内存地址创建 slice</span>
|
|
|
|
<span class="caption">示例 19-7: 通过任意内存地址创建 slice</span>
|
|
|
|
|
|
|
|
|
|
|
|
我们并不拥有这个任意地址的内存,也不能保证这段代码创建的 slice 包含有效的 `i32` 值。试图使用臆测为有效的 `slice` 会导致未定义的行为。
|
|
|
|
我们并不拥有这个任意地址的内存,也不能保证这段代码创建的 slice 包含有效的 `i32` 值。试图使用臆测为有效的 `slice` 会导致未定义的行为。如果我们没有注意将 `address` 向 4(字节)对齐(`i32` 的对齐方式),那么甚至调用 `slice::from_raw_parts_mut` 已经是为定义行为了 —— slice 必须总是对齐的,即使它没有被使用(哪怕甚至为空)。
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用 `extern` 函数调用外部代码
|
|
|
|
#### 使用 `extern` 函数调用外部代码
|
|
|
|
|
|
|
|
|
|
|
|
有时你的 Rust 代码可能需要与其他语言编写的代码交互。为此 Rust 有一个关键字,`extern`,有助于创建和使用 **外部函数接口**(*Foreign Function Interface*, FFI)。外部函数接口是一个编程语言用以定义函数的方式,其允许不同(外部)编程语言调用这些函数。
|
|
|
|
有时你的 Rust 代码可能需要与其他语言编写的代码交互。为此 Rust 有一个关键字,`extern`,有助于创建和使用 **外部函数接口**(*Foreign Function Interface*, FFI)。外部函数接口是一个编程语言用以定义函数的方式,其允许不同(外部)编程语言调用这些函数。
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Can you give a definition for FFI? -->
|
|
|
|
|
|
|
|
<!-- Done /Carol -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
示例 19-8 展示了如何集成 C 标准库中的 `abs` 函数。`extern` 块中声明的函数在 Rust 代码中总是不安全的。因为其他语言不会强制执行 Rust 的规则且 Rust 无法检查它们,所以确保其安全是程序员的责任:
|
|
|
|
示例 19-8 展示了如何集成 C 标准库中的 `abs` 函数。`extern` 块中声明的函数在 Rust 代码中总是不安全的。因为其他语言不会强制执行 Rust 的规则且 Rust 无法检查它们,所以确保其安全是程序员的责任:
|
|
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,unsafe
|
|
|
|
extern "C" {
|
|
|
|
extern "C" {
|
|
|
|
fn abs(input: i32) -> i32;
|
|
|
|
fn abs(input: i32) -> i32;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -258,29 +237,20 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
在 `extern "C"` 块中,列出了我们希望能够调用的另一个语言中的外部函数的签名和名称。`"C"` 部分定义了外部函数所使用的 **应用程序接口**(*application binary interface*,ABI) —— ABI 定义了如何在汇编语言层面调用此函数。`"C"` ABI 是最常见的,并遵循 C 编程语言的 ABI。
|
|
|
|
在 `extern "C"` 块中,列出了我们希望能够调用的另一个语言中的外部函数的签名和名称。`"C"` 部分定义了外部函数所使用的 **应用程序接口**(*application binary interface*,ABI) —— ABI 定义了如何在汇编语言层面调用此函数。`"C"` ABI 是最常见的,并遵循 C 编程语言的 ABI。
|
|
|
|
|
|
|
|
|
|
|
|
<!-- PROD: START BOX -->
|
|
|
|
> #### 从其它语言调用 Rust 函数
|
|
|
|
|
|
|
|
>
|
|
|
|
##### 通过其它语言调用 Rust 函数
|
|
|
|
> 也可以使用 `extern` 来创建一个允许其他语言调用 Rust 函数的接口。不同于 `extern` 块,就在 `fn` 关键字之前增加 `extern` 关键字并指定所用到的 ABI。还需增加 `#[no_mangle]` 注解来告诉 Rust 编译器不要 mangle 此函数的名称。*Mangling* 发生于当编译器将我们指定的函数名修改为不同的名称时,这会增加用于其他编译过程的额外信息,不过会使其名称更难以阅读。每一个编程语言的编译器都会以稍微不同的方式 mangle 函数名,所以为了使 Rust 函数能在其他语言中指定,必须禁用 Rust 编译器的 name mangling。
|
|
|
|
|
|
|
|
>
|
|
|
|
也可以使用 `extern` 来创建一个允许其他语言调用 Rust 函数的接口。不同于 `extern` 块,就在 `fn` 关键字之前增加 `extern` 关键字并指定所用到的 ABI。还需增加 `#[no_mangle]` 注解来告诉 Rust 编译器不要 mangle 此函数的名称。mangle 发生于当编译器将我们指定的函数名修改为不同的名称时,这会增加用于其他编译过程的额外信息,不过会使其名称更难以阅读。每一个编程语言的编译器都会以稍微不同的方式 mangle 函数名,所以为了使 Rust 函数能在其他语言中指定,必须禁用 Rust 编译器的 name mangling。
|
|
|
|
> 在如下的例子中,一旦其编译为动态库并从 C 语言中链接,`call_from_c` 函数就能够在 C 代码中访问:
|
|
|
|
|
|
|
|
>
|
|
|
|
<!-- have we discussed mangling before this? It doesn't ring a bell with me,
|
|
|
|
> ```rust
|
|
|
|
though it may have been in an early chapter that I forgot --- if not could you
|
|
|
|
> #[no_mangle]
|
|
|
|
give a quick explanation here? -->
|
|
|
|
> pub extern "C" fn call_from_c() {
|
|
|
|
<!-- I've tried, without going into too much detail! /Carol -->
|
|
|
|
> println!("Just called a Rust function from C!");
|
|
|
|
|
|
|
|
> }
|
|
|
|
在如下的例子中,一旦其编译为动态库并从 C 语言中链接,`call_from_c` 函数就能够在 C 代码中访问:
|
|
|
|
> ```
|
|
|
|
|
|
|
|
>
|
|
|
|
```rust
|
|
|
|
> `extern` 的使用无需 `unsafe`。
|
|
|
|
#[no_mangle]
|
|
|
|
|
|
|
|
pub extern "C" fn call_from_c() {
|
|
|
|
|
|
|
|
println!("Just called a Rust function from C!");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
`extern` 的使用无需 `unsafe`。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- PROD: END BOX -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 访问或修改可变静态变量
|
|
|
|
### 访问或修改可变静态变量
|
|
|
|
|
|
|
|
|
|
|
@ -308,7 +278,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,unsafe
|
|
|
|
static mut COUNTER: u32 = 0;
|
|
|
|
static mut COUNTER: u32 = 0;
|
|
|
|
|
|
|
|
|
|
|
|
fn add_to_count(inc: u32) {
|
|
|
|
fn add_to_count(inc: u32) {
|
|
|
@ -336,7 +306,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
最后一个只能用在 `unsafe` 中的操作是实现不安全 trait。当至少有一个方法中包含编译器不能验证的不变量时 trait 是不安全的。可以在 `trait` 之前增加 `unsafe` 关键字将 trait 声明为 `unsafe`,同时 trait 的实现也必须标记为 `unsafe`,如示例 19-11 所示:
|
|
|
|
最后一个只能用在 `unsafe` 中的操作是实现不安全 trait。当至少有一个方法中包含编译器不能验证的不变量时 trait 是不安全的。可以在 `trait` 之前增加 `unsafe` 关键字将 trait 声明为 `unsafe`,同时 trait 的实现也必须标记为 `unsafe`,如示例 19-11 所示:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust,unsafe
|
|
|
|
unsafe trait Foo {
|
|
|
|
unsafe trait Foo {
|
|
|
|
// methods go here
|
|
|
|
// methods go here
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -354,4 +324,4 @@ unsafe impl Foo for i32 {
|
|
|
|
|
|
|
|
|
|
|
|
### 何时使用不安全代码
|
|
|
|
### 何时使用不安全代码
|
|
|
|
|
|
|
|
|
|
|
|
使用 `unsafe` 来进行这四个操作之一是没有问题的,甚至是不需要深思熟虑的,不过使得 `unsafe` 代码正确也实属不易因为编译器不能帮助保证内存安全。当有理由使用 `unsafe` 代码时,是可以这么做的,通过使用显式的 `unsafe` 标注使得在出现错误时易于追踪问题的源头。
|
|
|
|
使用 `unsafe` 来进行这四个操作(超级力量)之一是没有问题的,甚至是不需要深思熟虑的,不过使得 `unsafe` 代码正确也实属不易因为编译器不能帮助保证内存安全。当有理由使用 `unsafe` 代码时,是可以这么做的,通过使用显式的 `unsafe` 标注使得在出现错误时易于追踪问题的源头。
|
|
|
|