mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
Merge pull request #406 from chikaku/master
Update ch19-01-unsafe-rust.md
This commit is contained in:
commit
6b3985a569
@ -12,7 +12,7 @@
|
||||
|
||||
### 不安全的超级力量
|
||||
|
||||
可以通过 `unsafe` 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有四类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,它们称之为 “不安全的超级力量。” 这些超级力量是:
|
||||
可以通过 `unsafe` 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,它们称之为 “不安全的超级力量。” 这些超级力量是:
|
||||
|
||||
* 解引用裸指针
|
||||
* 调用不安全的函数或方法
|
||||
@ -20,15 +20,15 @@
|
||||
* 实现不安全 trait
|
||||
* 访问 `union` 的字段
|
||||
|
||||
有一点很重要,`unsafe` 并不会关闭借用检查器或禁用任何其他 Rust 安全检查:如果在不安全代码中使用引用,它仍会被检查。`unsafe` 关键字只是提供了那四个不会被编译器检查内存安全的功能。你仍然能在不安全块中获得某种程度的安全。
|
||||
有一点很重要,`unsafe` 并不会关闭借用检查器或禁用任何其他 Rust 安全检查:如果在不安全代码中使用引用,它仍会被检查。`unsafe` 关键字只是提供了那五个不会被编译器检查内存安全的功能。你仍然能在不安全块中获得某种程度的安全。
|
||||
|
||||
再者,`unsafe` 不意味着块中的代码就一定是危险的或者必然导致内存安全问题:其意图在于作为程序员你将会确保 `unsafe` 块中的代码以有效的方式访问内存。
|
||||
|
||||
人是会犯错误的,错误总会发生,不过通过要求这四类操作必须位于标记为 `unsafe` 的块中,就能够知道任何与内存安全相关的错误必定位于 `unsafe` 块内。保持 `unsafe` 块尽可能小,如此当之后调查内存 bug 时就会感谢你自己了。
|
||||
人是会犯错误的,错误总会发生,不过通过要求这五类操作必须位于标记为 `unsafe` 的块中,就能够知道任何与内存安全相关的错误必定位于 `unsafe` 块内。保持 `unsafe` 块尽可能小,如此当之后调查内存 bug 时就会感谢你自己了。
|
||||
|
||||
为了尽可能隔离不安全代码,将不安全代码封装进一个安全的抽象并提供安全 API 是一个好主意,当我们学习不安全函数和方法时会讨论到。标准库的一部分被实现为在被评审过的不安全代码之上的安全抽象。这个技术防止了 `unsafe` 泄露到所有你或者用户希望使用由 `unsafe` 代码实现的功能的地方,因为使用其安全抽象是安全的。
|
||||
|
||||
让我们按顺序依次介绍上述四个超级力量,同时我们会看到一些提供不安全代码的安全接口的抽象。
|
||||
让我们按顺序依次介绍上述五个超级力量,同时我们会看到一些提供不安全代码的安全接口的抽象。
|
||||
|
||||
### 解引用裸指针
|
||||
|
||||
@ -182,7 +182,7 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
||||
|
||||
unsafe {
|
||||
(slice::from_raw_parts_mut(ptr, mid),
|
||||
slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
|
||||
slice::from_raw_parts_mut(ptr.add(mid), len - mid))
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -191,9 +191,9 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
||||
|
||||
回忆第四章的 [“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` 上调用 `offset` 方法并使用 `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` 函数是不安全的因为它获取一个裸指针,并必须确信这个指针是有效的。裸指针上的 `offset` 方法也是不安全的,因为其必须确信此地址偏移量也是有效的指针。因此必须将 `slice::from_raw_parts_mut` 和 `offset` 放入 `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` 代码,因为其只从这个函数访问的数据中创建了有效的指针。
|
||||
|
||||
@ -321,11 +321,15 @@ unsafe impl Foo for i32 {
|
||||
|
||||
通过 `unsafe impl`,我们承诺将保证编译器所不能验证的不变量。
|
||||
|
||||
作为一个例子,回忆第十六章 [“使用 `Sync` 和 `Send` trait 的可扩展并发”][extensible-concurrency-with-the-sync-and-send-traits] 部分中的 `Sync` 和 `Send` 标记 trait,编译器会自动为完全由 `Send` 和 `Sync` 类型组成的类型自动实现他们。如果实现了一个包含一些不是 `Send` 或 `Sync` 的类型,比如裸指针,并希望将此类型标记为 `Send` 或 `Sync`,则必须使用 `unsafe`。Rust 不能验证我们的类型保证可以安全的跨线程发送或在多线程键访问,所以需要我们自己进行检查并通过 `unsafe` 表明。
|
||||
作为一个例子,回忆第十六章 [“使用 `Sync` 和 `Send` trait 的可扩展并发”][extensible-concurrency-with-the-sync-and-send-traits] 部分中的 `Sync` 和 `Send` 标记 trait,编译器会自动为完全由 `Send` 和 `Sync` 类型组成的类型自动实现他们。如果实现了一个包含一些不是 `Send` 或 `Sync` 的类型,比如裸指针,并希望将此类型标记为 `Send` 或 `Sync`,则必须使用 `unsafe`。Rust 不能验证我们的类型保证可以安全的跨线程发送或在多线程间访问,所以需要我们自己进行检查并通过 `unsafe` 表明。
|
||||
|
||||
### 访问联合体中的字段
|
||||
|
||||
`union` 和 `struct` 类似,但是在一个实例中同时只能使用一个声明的字段。联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,因为 Rust 无法保证当前存储在联合体实例中数据的类型。可以查看[参考文档][reference]了解有关联合体的更多信息。
|
||||
|
||||
### 何时使用不安全代码
|
||||
|
||||
使用 `unsafe` 来进行这四个操作(超级力量)之一是没有问题的,甚至是不需要深思熟虑的,不过使得 `unsafe` 代码正确也实属不易,因为编译器不能帮助保证内存安全。当有理由使用 `unsafe` 代码时,是可以这么做的,通过使用显式的 `unsafe` 标注使得在出现错误时易于追踪问题的源头。
|
||||
使用 `unsafe` 来进行这五个操作(超级力量)之一是没有问题的,甚至是不需要深思熟虑的,不过使得 `unsafe` 代码正确也实属不易,因为编译器不能帮助保证内存安全。当有理由使用 `unsafe` 代码时,是可以这么做的,通过使用显式的 `unsafe` 标注使得在出现错误时易于追踪问题的源头。
|
||||
|
||||
[dangling-references]:
|
||||
ch04-02-references-and-borrowing.html#dangling-references
|
||||
@ -334,3 +338,4 @@ ch03-01-variables-and-mutability.html#differences-between-variables-and-constant
|
||||
[extensible-concurrency-with-the-sync-and-send-traits]:
|
||||
ch16-04-extensible-concurrency-sync-and-send.html#extensible-concurrency-with-the-sync-and-send-traits
|
||||
[the-slice-type]: ch04-03-slices.html#the-slice-type
|
||||
[reference]: https://doc.rust-lang.org/reference/items/unions.html
|
||||
|
Loading…
Reference in New Issue
Block a user