====== 第十七章 Unsafe Rust ======
Rust的安全保证是其最大的卖点,但有时这些保证会阻止你完成某些任务。Unsafe Rust允许你执行一些编译器无法保证内存安全的操作。本章将详细介绍何时以及如何使用Unsafe Rust。
===== 1. 为什么要使用Unsafe =====
==== 1.1 Safe Rust的限制 ====
Rust编译器强制执行许多安全规则:
* 禁止空指针解引用
* 禁止数据竞争
* 禁止缓冲区溢出
* 禁止使用未初始化内存
但在某些情况下,这些限制过于严格:
* 与C/C++代码交互(FFI)
* 实现底层数据结构(如Vec、HashMap)
* 直接使用硬件
* 极致性能优化
==== 1.2 Unsafe Rust的能力 ====
Unsafe Rust允许你执行五种操作:
* 解引用原始指针
* 调用unsafe函数或方法
* 访问或修改可变静态变量
* 实现unsafe trait
* 访问union的字段
**重要**:unsafe并不意味着代码一定危险或内存不安全。它只是将保证安全的责任从编译器转移到了程序员。
===== 2. 原始指针(Raw Pointers) =====
==== 2.1 原始指针类型 ====
Rust有两种原始指针:
* ***const T**:不可变原始指针,类似于&T
* ***mut T**:可变原始指针,类似于&mut T
fn main() {
let mut num = 5;
// 创建原始指针
let r1 = &num as *const i32; // 不可变原始指针
let r2 = &mut num as *mut i32; // 可变原始指针
// 注意:这里在safe代码中创建了原始指针是允许的
// 但解引用它们需要unsafe块
}
==== 2.2 解引用原始指针 ====
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1); // 5
println!("r2 is: {}", *r2); // 5
*r2 = 10;
println!("修改后 r1 is: {}", *r1); // 10
}
}
==== 2.3 原始指针与引用的区别 ====
| 特性 | 原始指针 | 引用 |
| 允许空值 | 是 | 否 |
| 允许多个可变指针 | 是 | 否 |
| 不保证指向有效内存 | 是 | 否 |
| 自动释放 | 否 | 是 |
| 实现Send/Sync | 是 | 有条件 |
==== 2.4 原始指针的使用场景 ====
// 与C代码交互
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("C abs(-3) = {}", abs(-3));
}
}
// 实现自己的智能指针
struct MyBox {
ptr: *mut T,
}
impl MyBox {
fn new(x: T) -> MyBox {
let ptr = Box::into_raw(Box::new(x));
MyBox { ptr }
}
fn get(&self) -> &T {
unsafe { &*self.ptr }
}
fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
}
}
impl Drop for MyBox {
fn drop(&mut self) {
unsafe {
Box::from_raw(self.ptr); // 重新获取所有权并释放
}
}
}
fn main() {
let mut b = MyBox::new(5);
println!("{}", b.get());
*b.get_mut() = 10;
println!("{}", b.get());
}
===== 3. Unsafe函数和方法 =====
==== 3.1 声明unsafe函数 ====
unsafe fn dangerous() {
println!("这是一个unsafe函数");
}
fn main() {
unsafe {
dangerous(); // 必须在unsafe块中调用
}
}
==== 3.2 创建safe的抽象 ====
最佳实践是将unsafe代码封装在safe的API中:
use std::slice;
// 对外暴露safe接口
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
assert!(mid <= len);
// 内部使用unsafe
unsafe {
(
slice::from_raw_parts_mut(slice.as_mut_ptr(), mid),
slice::from_raw_parts_mut(slice.as_mut_ptr().add(mid), len - mid),
)
}
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let (left, right) = split_at_mut(&mut v, 2);
println!("left: {:?}, right: {:?}", left, right);
}
==== 3.3 使用extern函数 =====
**调用C代码**:
// 声明C函数
#[link(name = "c")]
extern "C" {
fn printf(format: *const u8, ...) -> i32;
fn strlen(s: *const u8) -> usize;
}
fn main() {
unsafe {
let msg = b"Hello, C!\0";
printf(msg.as_ptr() as *const u8);
let s = b"Rust\0";
let len = strlen(s.as_ptr());
println!("长度: {}", len);
}
}
**导出Rust函数给C**:
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
// 使用panic时防止未定义行为
#[no_mangle]
pub extern "C" fn safe_add(a: i32, b: i32) -> i32 {
std::panic::catch_unwind(|| {
a + b
}).unwrap_or(0)
}
===== 4. 可变静态变量 =====
==== 4.1 全局可变状态 ====
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
**警告**:可变静态变量不是线程安全的,多线程访问会导致数据竞争。
==== 4.2 使用同步原语替代 ====
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
fn add_to_count(inc: u32) {
COUNTER.fetch_add(inc, Ordering::SeqCst);
}
fn main() {
add_to_count(3);
println!("COUNTER: {}", COUNTER.load(Ordering::SeqCst));
}
===== 5. Unsafe Trait =====
==== 5.1 声明unsafe trait ====
当trait的某些不变量编译器无法验证时,使用unsafe trait。
unsafe trait Foo {
// 方法定义
}
unsafe impl Foo for i32 {
// 实现
}
==== 5.2 标准库中的unsafe trait ====
**Send和Sync**:
// 实现Send表示类型可以安全地在线程间转移所有权
unsafe impl Send for MyType {}
// 实现Sync表示类型可以安全地被多个线程共享引用
unsafe impl Sync for MyType {}
**GlobalAlloc**:
use std::alloc::{GlobalAlloc, Layout, System};
struct MyAllocator;
unsafe impl GlobalAlloc for MyAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout)
}
}
#[global_allocator]
static ALLOCATOR: MyAllocator = MyAllocator;
===== 6. Union类型 =====
Union类似于C的union,所有字段共享同一内存位置。
#[repr(C)]
union MyUnion {
i: i32,
f: f32,
}
fn main() {
let mut u = MyUnion { i: 1 };
unsafe {
println!("作为i32: {}", u.i);
u.f = 3.14;
println!("作为f32: {}", u.f);
}
}
===== 7. 实现不安全的数据结构 =====
==== 7.1 实现自己的Vec ====
use std::alloc::{self, Layout};
use std::ptr::{self, NonNull};
struct MyVec {
ptr: NonNull,
len: usize,
capacity: usize,
}
impl MyVec {
fn new() -> Self {
Self {
ptr: NonNull::dangling(),
len: 0,
capacity: 0,
}
}
fn push(&mut self, elem: T) {
if self.len == self.capacity {
self.grow();
}
unsafe {
ptr::write(self.ptr.as_ptr().add(self.len), elem);
}
self.len += 1;
}
fn pop(&mut self) -> Option {
if self.len == 0 {
return None;
}
self.len -= 1;
unsafe {
Some(ptr::read(self.ptr.as_ptr().add(self.len)))
}
}
fn grow(&mut self) {
let new_capacity = if self.capacity == 0 { 1 } else { self.capacity * 2 };
let new_layout = Layout::array::(new_capacity).unwrap();
let new_ptr = if self.capacity == 0 {
unsafe { alloc::alloc(new_layout) as *mut T }
} else {
let old_layout = Layout::array::(self.capacity).unwrap();
unsafe {
alloc::realloc(
self.ptr.as_ptr() as *mut u8,
old_layout,
new_layout.size(),
) as *mut T
}
};
self.ptr = NonNull::new(new_ptr).expect("分配失败");
self.capacity = new_capacity;
}
fn len(&self) -> usize {
self.len
}
fn capacity(&self) -> usize {
self.capacity
}
fn get(&self, index: usize) -> Option<&T> {
if index >= self.len {
return None;
}
unsafe {
Some(&*self.ptr.as_ptr().add(index))
}
}
}
impl Drop for MyVec {
fn drop(&mut self) {
if self.capacity == 0 {
return;
}
// 逐个drop元素
for i in 0..self.len {
unsafe {
ptr::drop_in_place(self.ptr.as_ptr().add(i));
}
}
// 释放内存
let layout = Layout::array::(self.capacity).unwrap();
unsafe {
alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
}
}
fn main() {
let mut vec = MyVec::new();
vec.push(1);
vec.push(2);
vec.push(3);
println!("长度: {}, 容量: {}", vec.len(), vec.capacity());
while let Some(val) = vec.pop() {
println!("弹出: {}", val);
}
}
==== 7.2 实现双向链表(简化版) ====
use std::ptr::NonNull;
struct Node {
elem: T,
next: Option>>,
prev: Option>>,
}
pub struct LinkedList {
head: Option>>,
tail: Option>>,
len: usize,
}
impl LinkedList {
pub fn new() -> Self {
Self {
head: None,
tail: None,
len: 0,
}
}
pub fn push_front(&mut self, elem: T) {
let new_node = Box::new(Node {
elem,
next: self.head,
prev: None,
});
let new_node = NonNull::new(Box::into_raw(new_node));
match self.head {
Some(mut head) => unsafe {
head.as_mut().prev = new_node;
},
None => self.tail = new_node,
}
self.head = new_node;
self.len += 1;
}
pub fn pop_front(&mut self) -> Option {
self.head.map(|node| unsafe {
let node = Box::from_raw(node.as_ptr());
self.head = node.next;
match self.head {
Some(mut head) => head.as_mut().prev = None,
None => self.tail = None,
}
self.len -= 1;
node.elem
})
}
}
impl Drop for LinkedList {
fn drop(&mut self) {
while self.pop_front().is_some() {}
}
}
fn main() {
let mut list = LinkedList::new();
list.push_front(1);
list.push_front(2);
list.push_front(3);
while let Some(val) = list.pop_front() {
println!("{}", val);
}
}
===== 8. 内存布局与repr属性 =====
==== 8.1 repr(C) ====
与C兼容的内存布局:
#[repr(C)]
struct Point {
x: f64,
y: f64,
}
#[repr(C)]
struct Rectangle {
top_left: Point,
bottom_right: Point,
}
==== 8.2 repr(packed) ====
去除填充字节,但可能降低性能:
#[repr(packed)]
struct PackedStruct {
a: u8,
b: u32, // 通常会有3字节填充,packed去除了填充
}
==== 8.3 repr(align) ====
指定对齐方式:
#[repr(align(64))]
struct CacheLineAligned {
data: [u8; 64],
}
fn main() {
assert_eq!(std::mem::align_of::(), 64);
}
===== 9. FFI(外部函数接口) =====
==== 9.1 与C库交互 ====
// 绑定到C标准库
#[link(name = "c")]
extern "C" {
fn malloc(size: usize) -> *mut c_void;
fn free(ptr: *mut c_void);
fn memset(ptr: *mut c_void, value: c_int, size: usize) -> *mut c_void;
}
use std::os::raw::{c_void, c_int};
fn main() {
unsafe {
let ptr = malloc(1024);
if ptr.is_null() {
panic!("内存分配失败");
}
memset(ptr, 0, 1024);
free(ptr);
}
}
==== 9.2 使用bindgen自动生成绑定 ====
安装bindgen:
cargo install bindgen-cli
生成绑定:
bindgen /usr/include/some_header.h -o src/bindings.rs
==== 9.3 回调函数 ====
C调用Rust回调:
// Rust代码
pub extern "C" fn rust_callback(data: i32) {
println!("从C收到数据: {}", data);
}
// 声明C函数
#[link(name = "some_c_lib")]
extern "C" {
fn register_callback(cb: extern "C" fn(i32));
fn trigger_callback();
}
fn main() {
unsafe {
register_callback(rust_callback);
trigger_callback();
}
}
==== 9.4 处理字符串 ====
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
fn rust_string_to_c(s: &str) -> CString {
CString::new(s).expect("字符串包含空字节")
}
unsafe fn c_string_to_rust(ptr: *const c_char) -> String {
CStr::from_ptr(ptr)
.to_str()
.expect("无效UTF-8")
.to_string()
}
fn main() {
let c_string = rust_string_to_c("Hello");
println!("C字符串: {:?}", c_string);
// 假设从C收到了一个字符串指针
// let rust_str = unsafe { c_string_to_rust(c_ptr) };
}
===== 10. 内联汇编 =====
Rust支持内联汇编(nightly版本):
#![feature(asm)]
fn main() {
let mut x: u64 = 4;
unsafe {
asm!(
"add {0}, {1}",
inout(reg) x,
in(reg) 5,
);
}
assert_eq!(x, 9);
}
稳定版本可以使用外部汇编文件或cc crate:
// build.rs
fn main() {
cc::Build::new()
.file("src/asm_code.S")
.compile("asm_code");
}
===== 11. 未定义行为(UB) =====
==== 11.1 常见的UB来源 ====
* 解引用空指针或悬空指针
* 读取未初始化内存
* 违反借用规则(同时拥有可变和不可变引用)
* 使用某些类型的错误值(如bool不是0或1)
* 数据竞争
* 执行不返回的函数后继续使用值
==== 11.2 避免UB的最佳实践 ====
// 1. 始终检查指针有效性
unsafe fn safe_deref(ptr: *const i32) -> Option {
if ptr.is_null() {
return None;
}
Some(*ptr)
}
// 2. 使用NonNull确保非空
use std::ptr::NonNull;
struct SafePtr {
ptr: NonNull,
}
// 3. 使用MaybeUninit处理未初始化内存
use std::mem::MaybeUninit;
fn create_array(mut f: F) -> [T; 100]
where
F: FnMut(usize) -> T,
{
let mut arr: [MaybeUninit; 100] =
unsafe { MaybeUninit::uninit().assume_init() };
for i in 0..100 {
arr[i] = MaybeUninit::new(f(i));
}
unsafe {
std::mem::transmute::<_, [T; 100]>(arr)
}
}
===== 12. Miri:检测UB的工具 =====
Miri是一个Rust解释器,可以检测未定义行为:
rustup component add miri
cargo miri test
// 这段代码会被Miri检测到UB
fn main() {
let ptr = Box::into_raw(Box::new(1));
unsafe {
// 错误:使用已经被释放的内存
drop(Box::from_raw(ptr));
println!("{}", *ptr); // UB!
}
}
===== 13. 性能优化中的Unsafe =====
==== 13.1 消除边界检查 ====
fn sum_array(arr: &[i32]) -> i32 {
let mut sum = 0;
unsafe {
// 我们知道索引是安全的
for i in 0..arr.len() {
sum += *arr.get_unchecked(i);
}
}
sum
}
==== 13.2 SIMD操作 ====
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[target_feature(enable = "avx2")]
unsafe fn add_vectors_avx2(a: &[f32], b: &[f32], c: &mut [f32]) {
let len = a.len();
let mut i = 0;
while i + 8 <= len {
let va = _mm256_loadu_ps(a.as_ptr().add(i));
let vb = _mm256_loadu_ps(b.as_ptr().add(i));
let vc = _mm256_add_ps(va, vb);
_mm256_storeu_ps(c.as_mut_ptr().add(i), vc);
i += 8;
}
// 处理剩余元素
for j in i..len {
c[j] = a[j] + b[j];
}
}
fn main() {
let a = vec![1.0f32; 16];
let b = vec![2.0f32; 16];
let mut c = vec![0.0f32; 16];
unsafe {
add_vectors_avx2(&a, &b, &mut c);
}
println!("{:?}", c);
}
===== 14. 安全抽象设计原则 =====
==== 14.1 最小化unsafe代码 ====
// 不好:整个函数都是unsafe
pub unsafe fn process_data(data: *mut u8, len: usize) {
// ...
}
// 好:将unsafe封装在内部
pub fn process_data(data: &mut [u8]) {
unsafe {
// 只在必要时使用unsafe
}
}
==== 14.2 文档化安全不变量 ====
/// # Safety
///
/// 调用者必须保证:
/// - `ptr`指向有效的内存
/// - `ptr`对齐到T的对齐要求
/// - `ptr`指向的内存不会被同时访问
pub unsafe fn read_data(ptr: *const T) -> T {
ptr::read(ptr)
}
==== 14.3 使用assert!进行调试检查 ====
pub unsafe fn read_unchecked(slice: &[T], index: usize) -> &T {
debug_assert!(index < slice.len(), "索引越界");
slice.get_unchecked(index)
}
===== 小结 =====
本章深入探讨了Unsafe Rust:
* **原始指针**:*const T和*mut T,无安全检查的指针
* **unsafe函数/块**:包含unsafe操作的代码块
* **FFI**:与C/C++代码交互
* **可变静态变量**:全局可变状态
* **unsafe trait**:如Send、Sync、GlobalAlloc
* **Union**:共享内存布局的类型
使用Unsafe Rust的关键原则:
* 尽可能保持unsafe代码量最小
* 用safe包装器封装unsafe实现
* 充分文档化安全不变量
* 使用Miri等工具检测UB
* 让unsafe代码通过尽可能多的测试
Unsafe Rust是一把双刃剑,它赋予你极致的控制权和性能,但也要求你对内存模型和未定义行为有深入理解。合理使用unsafe,你可以在保持Rust安全保证的同时完成最底层的工作。