使用 unsafe
关键字将 Rust 安全代码和非安全代码建立边界。
unsafe {}
用于指示编译器忽略一些严格的安全检查(但不忽略所有检查), 主要使用场景:
- 读写裸指针(在安全代码中可以创建、转移、比较 裸指针,但是不能使用裸指针,如解引用和赋值);
- 调用外部库函数(FFI 函数);
- 调用 unsafe 函数或方法;
- 读写 static mut 全局变量;
- 实现 unsafe trait;
- 读写 union 对象;
unsafe
代码(func/block/trait)都有使用前提,使用方需要保证满足这些约束,防止出现未定义的行为。
Rust 编译器、type checker、borrow checker 和其它静态 checker 会详细检查代码的合法性,但 unsafe
代码不会有这些安全性优势,需要开发者自己来保证。常见做法:将 unsafe block 封装在安全的函数中。
// from_utf8_unchecked 是 unsaft 函数,只能在 unsafe 上下文中调用
unsafe {
String::from_utf8_unchecked(ascii)
}
// 调用外部库中的函数
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
// 读写 static mut 变量
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
// 在 unsafe block 中读写裸指针:
fn main() {
let mut a: usize = 0;
let ptr = &mut a as *mut usize;
unsafe {
*ptr.offset(3) = 0x7ffff72f484c;
}
}
// $ cargo build
// Compiling unsafe-samples v0.1.0
// Finished debug [unoptimized + debuginfo] target(s) in 0.44s
// $ ../../target/debug/crash
// crash: Error: .netrc file is readable by others.
// crash: Remove password or make file unreadable by others. Segmentation fault (core dumped)
裸指针的 unsafe 特性体现在:
- 允许忽略借用规则:可以同时拥有同一个内存地址的可变和不可变指针,或者拥有指向同一个地址的多个可变指针;
- 不能保证总是指向有效的内存地址;
- 允许为空(null);
fn main() {
let p;
{
let x = 5;
p = &x as *const i32; // 合法
}
unsafe {
// 未定义行为:p 悬垂了,但编译器不报错
println!("{}", *p); // 6
// 未定义行为:向只读对象写内容
let pm = p as *mut i32;
// 编译器不检查,不报错
*pm = 6;
println!("{}", *pm); // 6
}
}
unsafe function #
是添加了 unsafe 标识的函数,它的 body 是 unsafe block:
- unsafe 和普通函数完全一样,但用 unsafe 标记后表示调用该函数需要满足一些先决条件,需要开发者来保证。
- 只能在 unsafe function/block 中调用 unsafe 函数。
pub unsafe fn from_bytes_unchecked(bytes: Vec<u8>) -> Ascii {
Ascii(bytes)
}
// 使用 unsafe 特性实现的安全函数
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 {
// 需要在 unsafe block 中调用 unsafe 函数
(slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
}
}
use std::slice;
let address = 0x01234usize;
let r = address as *mut i32;
let slice : &[i32] = unsafe {
slice::from_raw_parts_mut(r, 10000)
};
unsafe trait #
是添加了 unsafe 标识的 trait,在 impl 它时也必须添加 unsafe 标识:
- unsafe trait 和普通 trait 完全一样,只是表明该 trait 的定义或实现有一些前提约束,Rust 编译器不会检查,需要开发者来保证。
// 定义 unsafe trait
pub unsafe trait Zeroable {}
// 实现 unsafe trait
unsafe impl Zeroable for u8 {}
unsafe impl Zeroable for i32 {}
unsafe impl Zeroable for usize {}
// 使用 unsafe trait 的函数或方法
use core::nonzero::Zeroable;
fn zeroed_vector<T>(len: usize) -> Vec<T> where T: Zeroable
{
let mut vec = Vec::with_capacity(len);
unsafe {
std::ptr::write_bytes(vec.as_mut_ptr(), 0, len);
vec.set_len(len);
}
vec
}