第二类要求使用不安全块的操作是调用不安全函数。不安全函数和方法与常规函数方法十分类似,除了
其开头有一个额外的 unsafe。在此上下文中,关键字unsafe表示该函数具有调用时需要满足的要求,而
Rust 不会保证满足这些要求。通过在 unsafe 块中调用不安全函数,表明我们已经阅读过此函数的文档
并对其是否满足函数自身的契约负责。
如下是一个没有做任何操作的不安全函数 dangerous 的例子:
fn main() {
unsafe fn dangerous() {}
unsafe {
dangerous();
}
}
必须在一个单独的 unsafe 块中调用 dangerous 函数。如果尝试不使用 unsafe 块调用 dangerous,则
会得到一个错误:
$ cargo run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
–> src/main.rs:4:5
|
4 | dangerous();
| ^^^^^^^^^^^ call to unsafe function
|
= note: consult the function’s documentation for information on how to avoid undefined behavior
For more information about this error, try
rustc --explain E0133
.
error: could not compile
unsafe-example
due to previous error
通过将 dangerous 调用插入 unsafe 块中,我们就向 Rust 保证了我们已经阅读过函数的文档,理解如
何正确使用,并验证过其满足函数的契约。
不安全函数体也是有效的 unsafe 块,所以在不安全函数中进行另一个不安全操作时无需新增额外的
unsafe 块。
创建不安全代码的安全抽象
仅仅因为函数包含不安全代码并不意味着整个函数都需要标记为不安全的。事实上,将不安全代码封装
进安全函数是一个常见的抽象。作为一个例子,标准库中的函数,split_at_mut,它需要一些不安全代
码,让我们探索如何可以实现它。这个安全函数定义于可变 slice 之上:它获取一个 slice 并从给定的索
引参数开始将其分为两个 slice。split_at_mut 的用法如示例 19-4 所示:
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let r = &mut v[…];
let (a, b) = r.split_at_mut(3);
assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);
}
示例 19-4: 使用安全的 split_at_mut 函数
这个函数无法只通过安全 Rust 实现。一个尝试可能看起来像示例 19-5,它不能编译。出于简单考虑,我
们将 split_at_mut 实现为函数而不是方法,并只处理 i32 值而非泛型 T 的 slice。
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
assert!(mid <= len);
(&mut slice[…mid], &mut slice[mid…])
}
fn main() {
let mut vector = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut vector, 3);
}
示例 19-5: 尝试只使用安全 Rust 来实现 split_at_mut
此函数首先获取 slice 的长度,然后通过检查参数是否小于或等于这个长度来断言参数所给定的索引位
于 slice 当中。该断言意味着如果传入的索引比要分割的 slice 的索引更大,此函数在尝试使用这个索引
前 panic。
之后我们在一个元组中返回两个可变的 slice:一个从原始 slice 的开头直到 mid 索引,另一个从 mid 直
到原 slice 的结尾。
如果尝试编译示例 19-5 的代码,会得到一个错误:
$ cargo run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
error[E0499]: cannot borrow
*slice
as mutable more than once at a time
–> src/main.rs:6:30
|
1 | fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
| - let’s call the lifetime of this reference
'1
…
6 | (&mut slice[…mid], &mut slice[mid…])
| -------------------------^^^^^--------
| | | |
| | | second mutable borrow occurs here
| | first mutable borrow occurs here
| returning this value requires that
*slice
is borrowed for
'1
For more information about this error, try
rustc --explain E0499
.
error: could not compile
unsafe-example
due to previous error
Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两
次。本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能
够理解这些。当我们知道某些事是可以的而 Rust 不知道的时候,就是触及不安全代码的时候了
示例 19-6 展示了如何使用 unsafe 块,裸指针和一些不安全函数调用来实现 split_at_mut:
use std::slice;
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
fn main() {
let mut vector = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut vector, 3);
}
示例 19-6: 在 split_at_mut 函数的实现中使用不安全代码
回忆第四章的 ”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 上调用 add 方法并使用 mid 作为参数来获取一个从 mid 开始的裸指针,使用这个裸指针并以 mid
之后项的数量为长度创建一个 slice。
slice :: from_raw_parts_mut 函数是不安全的因为它获取一个裸指针,并必须确信这个指针是有效
的。裸指针上的 add 方法也是不安全的,因为其必须确信此地址偏移量也是有效的指针。因此必须将
slice :: from_raw_parts_mut 和 add 放入 unsafe 块中以便能调用它们。通过观察代码,和增加 mid 必
然小于等于 len 的断言,我们可以说 unsafe 块中所有的裸指针将是有效的 slice 中数据的指针。这是一
个可以接受的 unsafe 的恰当用法。
注意无需将 split_at_mut 函数的结果标记为 unsafe,并可以在安全 Rust 中调用此函数。我们创建了一
个不安全代码的安全抽象,其代码以一种安全的方式使用了 unsafe 代码,因为其只从这个函数访问的
数据中创建了有效的指针。
与此相对,示例 19-7 中的 slice :: from_raw_parts_mut 在使用 slice 时很有可能会崩溃。这段代码获取
任意内存地址并创建了一个长为一万的 slice:
fn main() {
use std::slice;
let address = 0x01234usize;
let r = address as *mut i32;
let slice: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) };
}
示例 19-7: 通过任意内存地址创建 slice
我们并不拥有这个任意地址的内存,也不能保证这段代码创建的 slice 包含有效的 i32 值。试图使用臆测
为有效的 slice 会导致未定义的行为。
版权归原作者 完美句号 所有, 如有侵权,请联系我们删除。