Expand description
类型系统无法验证其 内存安全 的代码或接口。
unsafe
关键字有两种用途:
- 声明存在编译器无法检查的契约 (
unsafe fn
和unsafe trait
), - 并声明客户端已检查这些合同是否得到维护 (
不安全 {}
和unsafe impl
,还有unsafe fn
- 见下文)。
它们不是互斥的,正如在 unsafe fn
中可以看到的那样: 默认情况下,unsafe fn
的主体被视为不安全块。可以启用 unsafe_op_in_unsafe_fn
lint 来改变它。
不安全的能力
安全 Rust 无论如何都不会导致未定义行为。这被称为 可靠性: 一个良好类型的程序实际上具有期望的属性。在 Nomicon 中,对此主题有更详细的说明。
为了确保可靠性,安全 Rust 受到足够的限制,可以自动检查。然而,有时,出于一些聪明得让编译器无法理解的原因,有必要编写正确的代码。在这种情况下,您需要使用不安全的 Rust。
除安全 Rust 之外,不安全生锈还具有以下功能:
但是,这种额外的权力还伴随着额外的责任:现在由你来确保健全性。unsafe
关键字有助于清楚地标记需要担心这一点的代码段。
unsafe
的不同含义
并非所有的 unsafe
用法都是等价的:有些用法是为了标记程序员必须检查契约的存在,有些用法是为了表示 “我已经检查了契约,继续并执行此操作吧”。以下 关于 Rust 内部的讨论 对此有更深入的说明,但这里是要点的总结:
unsafe fn
: 调用这个函数意味着遵守编译器无法强制执行的契约。unsafe trait
: 实现trait
意味着遵守编译器无法强制执行的契约。unsafe {}
: 调用块内操作所必需的契约已经由程序员检查过,并保证得到遵守。unsafe impl
: 实现 trait 所必需的契约已经过程序员的检查,并保证得到遵守。
默认情况下,unsafe fn
也像函数中的代码周围的 unsafe {}
块一样。这意味着它不仅仅是给调用者的一个信号,而且还保证函数内部操作的先决条件得到支持。
混合这两种含义可能会令人困惑,因此可以启用 unsafe_op_in_unsafe_fn
lint 来警告这一点,并且即使在 unsafe fn
内部也需要明确的不安全块。
有关详细信息,请参见 Rustonomicon 和 Reference。
Examples
将元素标记为 unsafe
unsafe
可用在函数上。请注意,在 extern
块中声明的函数和静态变量被隐式标记为 unsafe
(而不是声明为 extern "something" fn ...
的函数)。
无论在何处声明,可变静态变量始终是不安全的。方法也可以被声明为 unsafe
:
static mut FOO: &str = "hello";
unsafe fn unsafe_fn() {}
extern "C" {
fn unsafe_extern_fn();
static BAR: *mut u32;
}
trait SafeTraitWithUnsafeMethod {
unsafe fn unsafe_method(&self);
}
struct S;
impl S {
unsafe fn unsafe_method_on_struct() {}
}
RunTraits 也可以被声明为 unsafe
:
unsafe trait UnsafeTrait {}
Run由于 unsafe fn
和 unsafe trait
表示存在编译器无法强制执行的安全保证,因此对其进行记录很重要。标准库提供了许多示例,例如以下示例,它摘录自 Vec::set_len
。
# Safety
部分解释了安全调用函数时必须履行的契约。
/// 将 vector 的长度强制为 `new_len`。
/// 这是一个低级操作,不维护该类型的任何正常不变量。
/// 通常,使用安全操作之一 (例如 `truncate`,`resize`,`extend` 或 `clear`) 来更改 vector 的长度。
/// # Safety
/// - `new_len` 必须小于或等于 `capacity()`。
/// - `old_len..new_len` 上的元素必须初始化。
pub unsafe fn set_len(&mut self, new_len: usize)
Run使用 unsafe {}
块和 impl
s
执行 unsafe
操作需要一个 unsafe {}
块:
#![deny(unsafe_op_in_unsafe_fn)]
/// 解引用给定的指针。
/// # Safety
/// `ptr` 必须对齐,并且不能是悬垂的。
unsafe fn deref_unchecked(ptr: *const i32) -> i32 {
// SAFETY: 调用者需要确保 `ptr` 对齐且可解引用。
unsafe { *ptr }
}
let a = 3;
let b = &a as *const _;
// SAFETY: `a` 尚未丢弃,并且引用始终对齐,因此 `b` 是有效地址。
unsafe { assert_eq!(*b, deref_unchecked(b)); };
Rununsafe
和 traits
unsafe
和 traits 的相互作用可能令人惊讶,所以让我们用两个例子来对比 unsafe trait
中的安全 fn
和安全 trait 中的 unsafe fn
的两种组合:
/// # Safety
/// `make_even` 必须返回一个偶数。
unsafe trait MakeEven {
fn make_even(&self) -> i32;
}
// SAFETY: 我们的 `make_even` 总是返回一些东西。
unsafe impl MakeEven for i32 {
fn make_even(&self) -> i32 {
self << 1
}
}
fn use_make_even(x: impl MakeEven) {
if x.make_even() % 2 == 1 {
// SAFETY: 这永远不会发生,因为所有 `MakeEven` 实现都确保 `make_even` 返回一些东西。
unsafe { std::hint::unreachable_unchecked() };
}
}
Run注意 trait 的安全保证是如何被实现维护的,它本身是用来维护 use_make_even
调用的不安全函数 unreachable_unchecked
的安全保证。
make_even
本身是一个安全的函数,因为它的callers 不必担心任何契约,只需要 MakeEven
的implementation 来维护某个契约。
use_make_even
是安全的,因为它可以使用 MakeEven
实现的 promise 来维护它调用的 unsafe fn unreachable_unchecked
的安全保证。
也可以在常规安全 trait
中包含 unsafe fn
:
#![deny(unsafe_op_in_unsafe_fn)]
trait Indexable {
const LEN: usize;
/// # Safety
/// 调用者必须确保 `idx < LEN`。
unsafe fn idx_unchecked(&self, idx: usize) -> i32;
}
// `i32` 的实现不需要做任何契约推理。
impl Indexable for i32 {
const LEN: usize = 1;
unsafe fn idx_unchecked(&self, idx: usize) -> i32 {
debug_assert_eq!(idx, 0);
*self
}
}
// 数组的实现利用函数契约在切片上使用 `get_unchecked` 并避免运行时检查。
impl Indexable for [i32; 42] {
const LEN: usize = 42;
unsafe fn idx_unchecked(&self, idx: usize) -> i32 {
// SAFETY: 根据此 trait 的文档,调用者确保 `idx < 42`.
unsafe { *self.get_unchecked(idx) }
}
}
// never type 的实现声明长度为 0,这意味着永远不能调用 `idx_unchecked`。
impl Indexable for ! {
const LEN: usize = 0;
unsafe fn idx_unchecked(&self, idx: usize) -> i32 {
// SAFETY: 根据这个 trait 的文档,调用者确保 `idx < 0`,这是不可能的,所以这是死代码。
unsafe { std::hint::unreachable_unchecked() }
}
}
fn use_indexable<I: Indexable>(x: I, idx: usize) -> i32 {
if idx < I::LEN {
// SAFETY: 我们已经检查了 `idx < I::LEN`。
unsafe { x.idx_unchecked(idx) }
} else {
panic!("index out-of-bounds")
}
}
Run这一次,use_indexable
是安全的,因为它使用运行时检查来解除 idx_unchecked
的安全保证。
实现 Indexable
是安全的,因为在编写 idx_unchecked
时,我们不必担心: 我们的callers 需要履行证明义务 (就像 use_indexable
一样),但 get_unchecked
的*实现 * 没有证明义务可抗衡。
当然,Indexable
的实现可以选择调用其他不安全的操作,然后它需要一个 unsafe
block 来表明它已经解除了被调用者的证明义务。
(我们启用了 unsafe_op_in_unsafe_fn
,因此 idx_unchecked
的主体并不是隐含的不安全块。) 为此,它可以使用所有调用者必须支持的契约 –idx < LEN
.
正式地说,trait 中的 unsafe fn
是一个具有preconditions 的函数,它超出了参数类型 (例如 idx < LEN
) 编码的那些,而 unsafe trait
可以声明它的某些函数具有postconditions 超出那些编码在返回类型 (例如返回偶数)。
如果 trait 需要一个具有额外前置条件和额外后置条件的函数,那么它需要一个 unsafe trait
中的 unsafe fn
。