Day 28: unsafe Rust
Rust的核心承诺是内存安全——通过所有权、借用检查和生命周期,在编译期消除大部分内存安全问题。但有些操作本质上是不安全的,如:
- 与C语言代码互操作(FFI)
- 访问硬件底层
- 实现其他unsafe trait
- 操作裸指针
Rust通过unsafe关键字标记这些不安全操作,允许开发者在受控范围内绕过安全检查。
2. unsafe的超能力
Section titled “2. unsafe的超能力”unsafe Rust相比普通Rust多了五个”超能力”:
- 解引用裸指针
- 调用不安全的函数或方法
- 访问或修改可变静态变量
- 实现unsafe trait
- 访问union的字段
fn main() { // 1. 解引用裸指针 - 可以在safe代码中进行 let raw_ptr = 5 as *const i32; unsafe { println!("裸指针解引用: {}", *raw_ptr); }}注意:裸指针的创建在safe代码中也可以,但解引用必须在unsafe块中。
3. unsafe函数
Section titled “3. unsafe函数”unsafe函数要求调用者显式处理安全性:
// 这个函数标记为unsafe,调用者必须验证安全性unsafe fn dangerous_operation(data: *mut i32) -> i32 { unsafe { *data // 解引用指针 }}
fn main() { let mut value = 42; // 调用unsafe函数需要unsafe块 unsafe { let result = dangerous_operation(&mut value as *mut i32); println!("结果: {}", result); }}unsafe函数是一种契约:函数本身无法保证安全性,调用者必须自己确保前提条件。
4. 创建裸指针
Section titled “4. 创建裸指针”裸指针有不可变的*const T和可变的*mut T两种:
fn main() { let mut value = 10;
// 从引用创建裸指针 let const_ptr: *const i32 = &value; let mut_ptr: *mut i32 = &mut value;
println!("const_ptr points to: {}", unsafe { *const_ptr }); println!("mut_ptr points to: {}", unsafe { *mut_ptr });
// 可以创建指向任意地址的指针(模拟硬件访问) let fake_ptr = 0x1000 as *mut i32; // unsafe { *fake_ptr = 0; } // 取消注释会导致未定义行为}5. FFI:调用C函数
Section titled “5. FFI:调用C函数”Rust能与C库无缝互操作,通过FFI(Foreign Function Interface)调用C函数:
// 声明外部C函数extern "C" { fn abs(x: i32) -> i32; // 假设有一个C函数 // int calculate(int x, int y); fn calculate(x: i32, y: i32) -> i32;}
#[repr(C)]struct Point { x: f64, y: f64,}
fn main() { // 调用C标准库的abs函数 unsafe { println!("abs(-5) = {}", abs(-5)); println!("calculate(10, 20) = {}", calculate(10, 20)); }
// 创建C兼容结构体 let p = Point { x: 1.0, y: 2.0 }; println!("C结构体大小: {}", std::mem::size_of::<Point>());}extern块声明了外部C函数,#[repr(C)]确保结构体内存布局与C兼容。
6. 编写暴露给C的函数
Section titled “6. 编写暴露给C的函数”除了调用C代码,Rust也可以导出函数给C调用:
#![allow(dead_code)]
// 标记为C ABI#[no_mangle]pub extern "C" fn rust_function(x: i32) -> i32 { x * 2}
// 导出的回调函数#[allow(dead_code)]pub extern "C" fn callback_from_c(data: *const i32, len: usize) -> i32 { unsafe { let slice = std::slice::from_raw_parts(data, len); slice.iter().sum() }}
// 从C的角度看,这些函数的ABI是兼容的#[no_mangle]防止编译器改名,extern "C"指定C ABI。
7. unsafe trait
Section titled “7. unsafe trait”某些trait的实现是不安全的,因为它们要求实现者保证某些不变性:
unsafe trait UnsafeTrait { fn method(&self) -> i32;}
// 实现unsafe trait也需要unsafe块unsafe impl UnsafeTrait for i32 { fn method(&self) -> i32 { *self }}标准库中的Send和Sync就是unsafe trait的例子——它们表示类型可以安全地在线程间传递/访问。
8. 可变静态变量
Section titled “8. 可变静态变量”在Rust中访问可变静态变量需要unsafe:
static mut COUNTER: i32 = 0;
fn increment() { unsafe { COUNTER += 1; println!("计数器: {}", COUNTER); }}
fn main() { increment(); increment(); increment();
unsafe { println!("最终值: {}", COUNTER); }}可变静态变量的访问需要unsafe,因为多线程并发访问会导致数据竞争。通常更好的做法是使用Mutex。
9. union访问
Section titled “9. union访问”Rust的union访问也需要unsafe:
#[repr(C)]union IntOrFloat { as_int: i32, as_float: f32,}
fn main() { let mut u = IntOrFloat { as_int: 42 };
unsafe { println!("作为i32: {}", u.as_int); u.as_float = 3.14; println!("作为f32: {}", u.as_float); println!("作为i32(重新解释): {}", u.as_int); }}Rust的union主要用于FFI场景,因为标准库已经有Option和Result来处理可选值。
10. 总结
Section titled “10. 总结”unsafe Rust是Rust类型系统的”出口”,让我们能够在必要场景下进行底层操作:
- unsafe块 解引用裸指针
- unsafe函数 标记需要调用者保证安全性
- FFI 与C代码互操作
- unsafe trait 标记需要实现者保证安全性的trait
- 可变静态变量 访问需要unsafe(但应避免)
使用unsafe的原则:隔离和最小化。将unsafe代码封装在安全的接口后,让大部分代码保持在safe Rust的安全保障内。