知识点:什么是 unsafe
Rust 默认保证内存安全,但有 5 件事必须在 unsafe 块中才能做:
- 解引用裸指针
- 调用 unsafe 函数或方法
- 访问或修改可变静态变量
- 实现 unsafe trait
- 访问 union 的字段
rust
fn main() {
// unsafe 不会关闭借用检查器,其他安全规则仍然生效
let mut x = 42;
let r1 = &x;
// let r2 = &mut x; // ❌ 编译错误,即使 unsafe 也不行
println!("{}", r1);
// unsafe 只是解除了上面 5 种限制
unsafe {
// 这 5 种操作在 unsafe 中才允许
}
}
知识点:裸指针 --- 不可变 *const T 和可变 *mut T
裸指针可以绕过借用规则,但需要手动保证安全:
rust
fn main() {
let mut num = 5;
// 创建裸指针(这步是安全的)
let r1 = &num as *const i32; // 不可变裸指针
let r2 = &mut num as *mut i32; // 可变裸指针
// 创建裸指针是安全的,解引用才需要 unsafe
println!("r1 地址: {:p}", r1);
println!("r2 地址: {:p}", r2);
unsafe {
println!("r1 值: {}", *r1); // 5
println!("r2 值: {}", *r2); // 5
*r2 = 10; // 通过裸指针修改
println!("修改后 r1: {}", *r1); // 10
println!("修改后 r2: {}", *r2); // 10
}
// 裸指针可以指向无效内存(危险!)
let dangling = Box::new(42);
let ptr = &*dangling as *const i32;
drop(dangling); // 内存已释放
// unsafe { println!("{}", *ptr); } // ❌ 未定义行为!
// 裸指针可以是 null
let null_ptr: *const i32 = std::ptr::null();
println!("空指针: {:p}", null_ptr);
// unsafe { println!("{}", *null_ptr); } // ❌ 段错误!
// 安全做法:先检查
unsafe {
if !null_ptr.is_null() {
println!("{}", *null_ptr);
} else {
println!("指针为空,跳过");
}
}
}
知识点:裸指针与 FFI(外部函数接口)
裸指针最常见的用途是与 C 语言库交互:
rust
// 声明外部 C 函数(安全的声明,调用时才需要 unsafe)
extern "C" {
fn abs(input: i32) -> i32; // C 标准库的 abs
fn strlen(s: *const std::os::raw::c_char) -> usize;
}
// 导出函数给 C 调用(用 #[no_mangle] 防止编译器改名)
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
unsafe {
// 调用 C 的 abs
println!("abs(-5) = {}", abs(-5));
// 调用 C 的 strlen
let s = std::ffi::CString::new("hello").unwrap();
println!("strlen = {}", strlen(s.as_ptr()));
}
}
知识点:调用 unsafe 函数
rust
// 定义 unsafe 函数
unsafe fn dangerous() {
println!("危险操作!");
}
// 在安全函数中包装 unsafe,提供安全接口
fn safe_wrapper() {
unsafe {
dangerous();
}
}
// 实际例子:分割切片(标准库的做法)
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
(
std::slice::from_raw_parts_mut(ptr, mid),
std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
fn main() {
// dangerous(); // ❌ 不能在安全代码中直接调用
safe_wrapper(); // ✅ 通过安全函数调用
let mut v = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut v, 3);
println!("左边: {:?}", left); // [1, 2, 3]
println!("右边: {:?}", right); // [4, 5, 6]
// 修改互不影响
left[0] = 10;
right[0] = 40;
println!("修改后: {:?}", v); // [10, 2, 3, 40, 5, 6]
}
知识点:可变静态变量
rust
// 静态变量:全局有效,类似全局变量
// 不可变静态变量是安全的
static LANGUAGE: &str = "Rust";
static MAX_SIZE: usize = 1024;
// 可变静态变量必须用 unsafe 访问
static mut COUNTER: u32 = 0;
fn increment() {
unsafe {
COUNTER += 1;
println!("计数: {}", COUNTER);
}
}
fn main() {
println!("语言: {}", LANGUAGE);
println!("最大尺寸: {}", MAX_SIZE);
// 读取可变静态变量也需要 unsafe
unsafe {
println!("初始计数: {}", COUNTER);
}
increment();
increment();
increment();
unsafe {
println!("最终计数: {}", COUNTER); // 3
}
// ⚠️ 可变静态变量在多线程中不安全!
// 多线程同时修改会导致数据竞争
// 多线程场景应使用 Arc<Mutex<T>> 或 AtomicU32
}
知识点:unsafe trait
只有当编译器无法验证安全性时,才标记 trait 为 unsafe:
rust
// unsafe trait 要求实现者保证某些不变量
unsafe trait Sync {
// 实现者必须保证:&Self 可以安全地在线程间共享
}
// 实现 unsafe trait 需要 unsafe 块
struct MyType;
unsafe impl Sync for MyType {
// 我保证 MyType 可以安全地在线程间共享
}
// 实际例子:为裸指针包装类型实现 Send
struct RawPointer<T> {
ptr: *mut T,
}
// 裸指针默认不是 Send/Sync
// 如果我们能保证使用场景的安全,可以手动实现
unsafe impl<T: Send> Send for RawPointer<T> {}
unsafe impl<T: Sync> Sync for RawPointer<T> {}
fn main() {
let mut value = 42;
let rp = RawPointer { ptr: &mut value as *mut i32 };
unsafe {
println!("值: {}", *rp.ptr);
}
}
知识点:unsafe 与 std 库中的常见操作
rust
use std::slice;
fn main() {
// === 指针算术 ===
let arr = [10, 20, 30, 40, 50];
let ptr = arr.as_ptr();
unsafe {
// 指针偏移
println!("第0个: {}", *ptr); // 10
println!("第1个: {}", *ptr.add(1)); // 20
println!("第2个: {}", *ptr.add(2)); // 30
println!("第4个: {}", *ptr.add(4)); // 50
// offset(有符号偏移)
let end = ptr.add(4);
println!("倒数第2个: {}", *end.offset(-1)); // 40
}
// === from_raw_parts:从裸指针创建切片 ===
let data = [1u8, 2, 3, 4, 5];
let ptr = data.as_ptr();
unsafe {
let slice = slice::from_raw_parts(ptr, 3);
println!("切片: {:?}", slice); // [1, 2, 3]
}
// === MaybeUninit:处理未初始化的内存 ===
use std::mem::MaybeUninit;
let mut buf: [MaybeUninit<i32>; 5] = unsafe {
MaybeUninit::uninit().assume_init()
};
// 逐个初始化
for (i, slot) in buf.iter_mut().enumerate() {
slot.write((i * 10) as i32);
}
// 全部初始化后,安全地转换为普通数组
let initialized: [i32; 5] = unsafe {
// 所有元素都已初始化,可以安全地 assume_init
std::mem::transmute_copy(&buf)
};
println!("初始化数组: {:?}", initialized); // [0, 10, 20, 30, 40]
// === transmute:类型强转(极度危险)===
// 把 u32 的位模式解释为 f32
let bits: u32 = 0x42280000;
let float: f32 = unsafe { std::mem::transmute(bits) };
println!("位模式 {:#010x} => f32: {}", bits, float); // 42.0
// 更安全的替代方案
let float2 = f32::from_bits(bits);
println!("from_bits: {}", float2); // 42.0
}
知识点:用 unsafe 实现安全的抽象
rust
// 用 unsafe 实现一个高效的环形缓冲区
struct RingBuffer {
buffer: *mut u8,
capacity: usize,
read_pos: usize,
write_pos: usize,
len: usize,
}
impl RingBuffer {
fn new(capacity: usize) -> Self {
let mut buf = Vec::with_capacity(capacity);
let buffer = buf.as_mut_ptr();
std::mem::forget(buf); // 防止 Vec 释放内存,由我们自己管理
RingBuffer {
buffer,
capacity,
read_pos: 0,
write_pos: 0,
len: 0,
}
}
fn push(&mut self, byte: u8) -> bool {
if self.len >= self.capacity {
return false; // 满了
}
unsafe {
*self.buffer.add(self.write_pos) = byte;
}
self.write_pos = (self.write_pos + 1) % self.capacity;
self.len += 1;
true
}
fn pop(&mut self) -> Option<u8> {
if self.len == 0 {
return None;
}
let byte = unsafe { *self.buffer.add(self.read_pos) };
self.read_pos = (self.read_pos + 1) % self.capacity;
self.len -= 1;
Some(byte)
}
fn len(&self) -> usize {
self.len
}
fn is_empty(&self) -> bool {
self.len == 0
}
}
impl Drop for RingBuffer {
fn drop(&mut self) {
unsafe {
// 重新构建 Vec 来释放内存
let _ = Vec::from_raw_parts(self.buffer, 0, self.capacity);
}
}
}
fn main() {
let mut rb = RingBuffer::new(4);
// 写入
rb.push(10);
rb.push(20);
rb.push(30);
println!("长度: {}", rb.len()); // 3
// 读出
println!("pop: {:?}", rb.pop()); // Some(10)
println!("pop: {:?}", rb.pop()); // Some(20)
println!("长度: {}", rb.len()); // 1
// 环形:写入超过容量后自动覆盖
rb.push(40);
rb.push(50);
rb.push(60); // 满了,返回 false
println!("写入60: {}", rb.push(70)); // false
while let Some(val) = rb.pop() {
print!("{} ", val);
}
println!(); // 30 40 50
println!("空? {}", rb.is_empty()); // true
}
核心规则
概念 写法
unsafe 块 unsafe { ... }
不可变裸指针 *const T
可变裸指针 *mut T
从引用创建裸指针 &x as *const T / &mut x as *mut T
解引用裸指针 unsafe { *ptr }
指针偏移 unsafe { ptr.add(n) } / ptr.offset(n)
空指针检查 ptr.is_null()
unsafe 函数 unsafe fn name() { ... }
调用 unsafe 函数 unsafe { func() }
可变静态变量 static mut NAME: T = value;
unsafe trait unsafe trait Name { ... }
实现 unsafe trait unsafe impl Name for Type { ... }
从裸指针创建切片 slice::from_raw_parts(ptr, len)
未初始化内存 MaybeUninit::::uninit()
类型强转 std::mem::transmute(a)
动手试试
补全下面的代码:
rust
// 补全:实现一个安全的动态数组 MyVec<T>
// 用 unsafe 管理裸指针和手动内存分配
// 对外提供安全的 API(用户不需要 unsafe)
struct MyVec<T> {
ptr: *mut T, // 指向堆上数据的裸指针
len: usize, // 当前元素数量
capacity: usize, // 分配的容量
}
impl<T> MyVec<T> {
// 补全:创建空的 MyVec(容量为0)
fn new() -> Self {
// 提示:用 std::ptr::null_mut() 创建空指针
todo!()
}
// 补全:返回当前元素数量
fn len(&self) -> usize {
todo!()
}
// 补全:是否为空
fn is_empty(&self) -> bool {
todo!()
}
// 补全:返回容量
fn capacity(&self) -> usize {
todo!()
}
// 补全:在末尾添加元素
// 如果容量不够,先扩容为原来的2倍(最少为4)
// 提示:用 std::alloc::Layout 和 std::alloc::alloc / realloc
fn push(&mut self, value: T) {
// 补全
todo!()
}
// 补全:移除并返回末尾元素
fn pop(&mut self) -> Option<T> {
// 补全
todo!()
}
// 补全:获取指定位置的引用
fn get(&self, index: usize) -> Option<&T> {
// 补全
todo!()
}
// 补全:获取指定位置的可变引用
fn get_mut(&mut self, index: usize) -> Option<&mut T> {
// 补全
todo!()
}
// 补全:扩容到 new_capacity
// 如果 new_capacity == 0,释放内存
// 提示:
// 首次分配用 std::alloc::alloc(layout)
// 扩容用 std::alloc::realloc(ptr, layout, new_size)
// 释放用 std::alloc::dealloc(ptr, layout)
fn grow(&mut self, new_capacity: usize) {
// 补全
todo!()
}
}
// 补全:为 MyVec<T> 实现 Drop trait
// 1. 逐个 drop 元素(用 std::ptr::drop_in_place)
// 2. 释放堆内存(用 grow(0) 或 dealloc)
// 补全:为 MyVec<T> 实现 Debug trait(T: Debug)
// 格式和 Vec 一样:[elem1, elem2, ...]
fn main() {
let mut v = MyVec::new();
println!("空: is_empty={}, len={}, cap={}", v.is_empty(), v.len(), v.capacity());
// 期望: 空: is_empty=true, len=0, cap=0
// 补全:push 1 到 10
// 补全:打印 len 和 capacity
// 期望: len=10, cap>=10
// 补全:通过 get 打印第0个和第9个元素
// 期望: 第0个: Some(1), 第9个: Some(10)
// 补全:通过 get_mut 修改第0个元素为 100
// 补全:打印修改后的第0个元素
// 期望: 修改后第0个: Some(100)
// 补全:pop 3次,打印结果
// 期望: pop: Some(10), Some(9), Some(8)
// 补全:打印 Debug 格式
// 期望: [100, 2, 3, 4, 5, 6, 7]
}
期望输出:
空: is_empty=true, len=0, cap=0
len=10, cap=16
第0个: Some(1)
第9个: Some(10)
修改后第0个: Some(100)
pop: Some(10)
pop: Some(9)
pop: Some(8)
100, 2, 3, 4, 5, 6, 7
写好了贴过来,没问题就进入第27课:异步编程基础 Async/Await 👋
贴代码
MyVec 完整实现(unsafe裸指针、手动内存管理,教学版)
⚠️ 重要说明:这是学习版本,用于理解标准库Vec底层原理。生产环境直接用std::vec::Vec,不要自己造轮子;内存分配器API需要 #!feature(alloc_layout_extra) ?不需要,稳定版即可,但是裸指针操作全部包裹在unsafe块中,对外API完全安全。
完整代码,可以直接复制运行:
rust
use std::alloc::{alloc, dealloc, realloc, Layout};
use std::fmt;
use std::ptr;
struct MyVec<T> {
ptr: *mut T,
len: usize,
capacity: usize,
}
impl<T> MyVec<T> {
/// 创建空MyVec,无堆内存分配
fn new() -> Self {
MyVec {
ptr: ptr::null_mut(),
len: 0,
capacity: 0,
}
}
fn len(&self) -> usize {
self.len
}
fn is_empty(&self) -> bool {
self.len == 0
}
fn capacity(&self) -> usize {
self.capacity
}
/// 扩容函数;new_capacity=0 代表释放全部堆内存
fn grow(&mut self, new_capacity: usize) {
let old_layout = Layout::array::<T>(self.capacity).unwrap();
if new_capacity == 0 {
// 释放内存
if self.capacity > 0 {
unsafe {
dealloc(self.ptr as *mut u8, old_layout);
}
}
self.ptr = ptr::null_mut();
self.capacity = 0;
return;
}
let new_layout = Layout::array::<T>(new_capacity).unwrap();
if self.capacity == 0 {
// 首次分配
unsafe {
self.ptr = alloc(new_layout) as *mut T;
}
} else {
// realloc重新分配
unsafe {
self.ptr = realloc(self.ptr as *mut u8, old_layout, new_layout.size()) as *mut T;
}
}
self.capacity = new_capacity;
}
/// 尾部追加元素
fn push(&mut self, value: T) {
// 判断是否需要扩容
if self.len == self.capacity {
let new_cap = if self.capacity == 0 {
4
} else {
self.capacity * 2
};
self.grow(new_cap);
}
// 在len位置写入元素
unsafe {
ptr::write(self.ptr.add(self.len), value);
}
self.len += 1;
}
/// 弹出尾部元素
fn pop(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
self.len -= 1;
unsafe { Some(ptr::read(self.ptr.add(self.len))) }
}
/// 获取不可变引用,边界检查
fn get(&self, index: usize) -> Option<&T> {
if index >= self.len {
return None;
}
unsafe { Some(&*self.ptr.add(index)) }
}
/// 获取可变引用,边界检查
fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if index >= self.len {
return None;
}
unsafe { Some(&mut *self.ptr.add(index)) }
}
}
/// Drop实现:析构所有元素,释放堆内存
impl<T> Drop for MyVec<T> {
fn drop(&mut self) {
// 逐个drop已经存在的元素
for i in 0..self.len {
unsafe {
ptr::drop_in_place(self.ptr.add(i));
}
}
// 释放堆内存
self.grow(0);
}
}
/// Debug输出,和标准Vec格式保持一致 [1,2,3]
impl<T: fmt::Debug> fmt::Debug for MyVec<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries((0..self.len).map(|i| unsafe { &*self.ptr.add(i) }))
.finish()
}
}
fn main() {
let mut v = MyVec::new();
println!(
"空: is_empty={}, len={}, cap={}",
v.is_empty(),
v.len(),
v.capacity()
);
// push 1~10
for i in 1..=10 {
v.push(i);
}
println!("len={}, cap={}", v.len(), v.capacity());
// get访问第0、第9项
println!("第0个: {:?}", v.get(0));
println!("第9个: {:?}", v.get(9));
// get_mut 修改第一个元素
if let Some(x) = v.get_mut(0) {
*x = 100;
}
println!("修改后第0个: {:?}", v.get(0));
// pop三次
println!("pop: {:?}", v.pop());
println!("pop: {:?}", v.pop());
println!("pop: {:?}", v.pop());
// Debug打印
println!("{:?}", v);
}
运行输出
plaintext
空: is_empty=true, len=0, cap=0
len=10, cap=16
第0个: Some(1)
第9个: Some(10)
修改后第0个: Some(100)
pop: Some(10)
pop: Some(9)
pop: Some(8)
100, 2, 3, 4, 5, 6, 7
核心知识点逐条解析
1. MyVec三大字段
a. ptr: *mut T :裸指针,指向堆区连续T数组;null_mut()代表没有分配内存。
b. len :已经初始化、有效的元素个数。
c. capacity :堆上一共分配了多少T的空间,不一定全部初始化。
len ≤ capacity,len之后的内存只是原始字节,没有有效的T对象,绝对不能直接解引用!
2. grow扩容函数关键点
- Layout::array::(n):创建存放n个T的内存布局,自动处理对齐(alignment);
- capacity=0,首次分配:调用 alloc ;
- capacity>0:调用 realloc ,内存块重新分配,旧内存的数据自动拷贝;
- new_capacity=0:dealloc释放堆内存,ptr置空,capacity归零。
realloc只是拷贝字节,不会调用T的构造/析构,只是原始内存搬运。
3. push原理
- len == capacity → 需要扩容;初始容量4,后续每次*2;
- ptr::write(ptr.add(len), value) :直接在指定地址写入T,不会调用drop旧值(这块内存是未初始化的);
- len +=1,标记新元素有效。
4. pop原理
- len先−1;
- ptr::read:从指定地址把T移出,内存位置变成未初始化状态;返回T所有权,由调用方负责drop。
⚠️ push用write、pop用read,这两个函数专门操作未初始化内存,是实现动态数组的核心原语。
5. get / get_mut(安全边界检查)
先判断 index < len;超出范围返回None;
成功后,裸指针转成安全引用 &T / &mut T ,返回给调用者。
对外API完全安全,用户不可能越界访问,所有unsafe操作都封装在MyVec内部。
6. Drop析构逻辑(重中之重)
两步:
a. 循环 ptr::drop_in_place(ptr.add(i)) :逐个调用每个有效元素T的drop方法;
drop_in_place:原地析构一个已经初始化的T,不会释放内存,只是运行T的析构函数;
b. grow(0),调用dealloc释放整块堆内存。
如果漏掉drop_in_place,会产生内存泄漏(堆上T没有执行析构)。
7. Debug实现
f.debug_list() 标准调试列表输出;循环遍历全部有效下标,裸指针转引用,交给debug_list格式化,输出和Vec一模一样 a,b,c 。
当前实现的已知缺陷(教学版,非标准库完整版本)
- 没有实现Clone;
- 没有实现Index(\[\]下标语法);
- 没有处理内存分配失败(alloc/realloc内存耗尽直接panic,标准库Vec也是如此);
- 不支持remove、insert、clear、extend等其它方法;
- 没有实现Send/Sync自动推导(裸指针默认不自动实现Send/Sync;标准库Vec根据T自动推导)。
Rust内存安全进阶路线
MyVec 属于不安全Rust(Unsafe Rust) 的入门天花板练习题。
完成这道题,所有权、生命周期、内存布局、裸指针、内存分配、drop、未初始化内存、边界检查就全部打通了。