Keyword unsafe[][src]

Expand description

memory safety 不能由类型系统验证的代码或接口。

unsafe 关键字有两个用途: 声明编译器无法检查的契约的存在 (unsafe fnunsafe trait),以及声明程序员检查了这些契约是否得到遵守 (unsafe {}unsafe impl,还有 unsafe fn - 见下文)。

它们并不互斥,如 unsafe fn 所示。

不安全能力

无论如何,安全 Rust 都不会导致未定义行为。这称为 soundness: 类型正确的程序实际上具有所需的属性。Nomicon 对此主题有更详细的说明。

为了确保安全,Safe Rust 受到足够的限制,可以自动检查。但是,有时出于某些原因,需要编写正确的代码,而这些原因对于编译器来说太聪明了。在这种情况下,您需要使用不安全的 Rust。

除安全 Rust 之外,以下还有不安全 Rust 所具有的功能:

但是,这种额外的权力还伴随着额外的责任: 现在要确保声音的完整性。unsafe 关键字有助于明确标记需要担心的代码段。

unsafe 的不同含义

并非 unsafe 的所有用法都是等效的: 这里有些标记是程序员必须检查的契约的存在,而另一些是 “I have checked the contract, go ahead and do this”。 以下 discussion on Rust Internals 对此有更深入的说明,但这是要点的总结:

  • unsafe fn: 调用此函数意味着遵守编译器无法强制执行的契约。
  • unsafe trait: 实现 trait 意味着遵守编译器无法执行的契约。
  • unsafe {}: 程序员已检查了在块中进行操作所必需的契约,并保证该契约得到遵守。
  • unsafe impl: 程序员已经检查了实现 trait 所需的契约,并保证该契约得到遵守。

unsafe fn 在函数内的代码周围,它也像一个 unsafe {} 块。这意味着不仅要向调用者发出信号,而且还应 promises 维护函数内部操作的前提条件。 混合使用这两种含义可能会造成混淆,并且存在在进行 unsafe 操作时在此类函数中使用 unsafe {} 块的建议。

有关更多信息,请参见 RustnomiconReference

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() {}
}
Run

Traits 也可以声明为 unsafe:

unsafe trait UnsafeTrait {}
Run

由于 unsafe fnunsafe 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 {} 块和 impls

执行 unsafe 操作需要一个 unsafe {} 块:

/// 解引用给定的指针。
/// # Safety
/// `ptr` 必须对齐并且不能悬空。
unsafe fn deref_unchecked(ptr: *const i32) -> i32 {
    *ptr
}

let a = 3;
let b = &a as *const _;
// SAFETY: `a` 尚未丢弃,并且引用始终对齐,因此 `b` 是有效地址。
unsafe { assert_eq!(*b, deref_unchecked(b)); };
Run

标记为 unsafe 的 Traits 必须使用 unsafe impl 进行复制。这为实现满足 trait 的安全保证的其他 unsafe 代码提供了保证。SendSync traits 是标准库中此行为的示例。

/// trait 的实现者必须保证元素始终可以通过索引 3 访问。
unsafe trait ThreeIndexable<T> {
    /// 返回 `&self` 中索引为 3 的元素的引用。
    fn three(&self) -> &T;
}

// `[T; 4]` 的 `ThreeIndexable` 实现是 `unsafe`,因为实现者必须遵守编译器无法检查的契约,但作为一名程序员,我们知道在索引 3 处总会有一个有效元素要访问。
unsafe impl<T> ThreeIndexable<T> for [T; 4] {
    fn three(&self) -> &T {
        // SAFETY: 实现 trait 意味着总是有一个索引为 3 的元素可以访问。
        unsafe { self.get_unchecked(3) }
    }
}

let a = [1, 2, 4, 8];
assert_eq!(a.three(), &8);
Run