Rust 中 &i32 与 *i32 的深度解析

发布于:2025-08-18 ⋅ 阅读:(17) ⋅ 点赞:(0)

Rust 中 &i32*i32 的深度解析

在 Rust 中,&i32*i32 是两种完全不同的指针类型,它们在安全性、所有权和使用方式上有本质区别。以下是详细对比:

核心区别概览

指针类型
引用 &i32
裸指针 *i32
安全
编译器检查
生命周期管理
不安全
手动管理
FFI/底层操作

详细对比分析

1. &i32 - 引用(安全指针)

特性 说明
安全性 100% 安全,编译器保证有效性
所有权 借用检查器控制,不转移所有权
解引用 自动解引用(Deref coercion)
使用场景 日常 Rust 编程
可变性 &i32(不可变)或 &mut i32(可变)
空值 永远不为空
别名规则 严格遵循借用规则(要么多个不可变引用,要么一个可变引用)
fn safe_ref_example() {
    let num = 42;
    let ref_num: &i32 = # // 创建不可变引用
    
    // 自动解引用
    println!("Value: {}", *ref_num); 
    println!("Value: {}", ref_num); // 同样有效
    
    // 编译错误:不能同时存在可变和不可变引用
    // let mut_ref = &mut num;
}

2. *i32 - 裸指针(不安全指针)

特性 说明
安全性 不安全,需要 unsafe 块操作
所有权 无所有权概念,可能悬垂
解引用 必须显式解引用(在 unsafe 中)
使用场景 FFI、底层系统编程、性能优化
可变性 *const i32(不可变)或 *mut i32(可变)
空值 可能为空(std::ptr::null()
别名规则 无编译时检查,需手动保证安全
fn raw_ptr_example() {
    let num = 42;
    let raw_ptr: *const i32 = &num as *const i32;
    
    // 必须使用 unsafe 块解引用
    unsafe {
        println!("Value: {}", *raw_ptr);
    }
    
    // 可能创建悬垂指针
    let dangling_ptr: *const i32;
    {
        let temp = 100;
        dangling_ptr = &temp as *const i32;
    } // temp 离开作用域
    // 危险:dangling_ptr 现在无效!
}

关键区别详解

1. 安全机制对比

操作
&i32
*i32
编译时检查
借用规则
生命周期验证
运行时风险
悬垂指针
数据竞争

2. 内存访问对比

操作 &i32 *i32
创建引用/指针 &value &value as *const i32
解引用 自动或显式 *ref 必须 unsafe { *ptr }
空指针 不可能 std::ptr::null()
指针算术 不支持 支持(ptr.offset(1)
类型转换 有限制(通过 trait) 自由转换(as *mut u8

3. 典型使用场景

&i32 场景:
// 函数参数传递
fn print_value(v: &i32) {
    println!("Value: {}", v);
}

// 结构体借用
struct Processor<'a> {
    data: &'a i32,
}

// 迭代器使用
let sum: i32 = vec![1, 2, 3].iter().map(|&x| x * 2).sum();
*i32 场景:
// C 语言交互
extern "C" {
    fn c_function(ptr: *mut i32);
}

// 自定义内存管理
unsafe fn allocate_buffer(size: usize) -> *mut u8 {
    let layout = std::alloc::Layout::array::<u8>(size).unwrap();
    std::alloc::alloc(layout)
}

// 高性能算法
unsafe fn fast_sum(ptr: *const i32, len: usize) -> i32 {
    let mut sum = 0;
    for i in 0..len {
        sum += *ptr.add(i);
    }
    sum
}

转换关系

1. 安全转换

// 引用 → 裸指针(安全)
let num = 42;
let ref_num: &i32 = &num;
let raw_ptr: *const i32 = ref_num as *const i32;

2. 不安全转换

// 裸指针 → 引用(不安全)
unsafe {
    let ref_back: &i32 = &*raw_ptr;
}

3. 转换注意事项

as *const
unsafe &*
as *const
FFI
Rust调用
安全世界
裸指针
引用
C语言

最佳实践指南

何时使用 &i32

  1. 99% 的日常 Rust 编程
  2. 函数参数传递
  3. 结构体字段借用
  4. 实现 trait 对象 (&dyn Trait)

何时使用 *i32

  1. 与 C 库交互(FFI)
  2. 实现底层数据结构(如自定义分配器)
  3. 极端性能优化场景
  4. 操作系统内核开发

安全使用裸指针的模式:

// 模式1:封装在安全API中
struct SafePointer {
    ptr: *mut i32,
    len: usize,
}

impl SafePointer {
    // 安全构造函数
    pub fn new(data: &mut [i32]) -> Self {
        SafePointer {
            ptr: data.as_mut_ptr(),
            len: data.len(),
        }
    }
    
    // 安全访问方法
    pub fn get(&self, index: usize) -> Option<&i32> {
        if index < self.len {
            unsafe { Some(&*self.ptr.add(index)) }
        } else {
            None
        }
    }
}

// 模式2:与生命周期绑定
struct GuardedPtr<'a> {
    ptr: *const i32,
    _marker: std::marker::PhantomData<&'a i32>,
}

impl<'a> GuardedPtr<'a> {
    pub fn new(reference: &'a i32) -> Self {
        GuardedPtr {
            ptr: reference as *const i32,
            _marker: std::marker::PhantomData,
        }
    }
    
    pub fn get(&self) -> &'a i32 {
        unsafe { &*self.ptr }
    }
}

性能对比

操作 &i32 *i32
创建开销 零成本抽象 零成本
解引用开销 等同直接访问 等同直接访问
安全检查开销 编译时(零运行时开销) 无检查(需手动验证)
优化潜力 编译器充分优化 同左,但需更多人工干预

常见错误示例

错误1:悬垂引用

fn dangling_ref() -> &'static i32 {
    let x = 42;
    &x // 错误:返回局部变量引用
}

错误2:不安全裸指针使用

fn unsafe_ptr_demo() {
    let ptr: *const i32;
    {
        let value = 100;
        ptr = &value;
    } // value 被丢弃
    unsafe {
        println!("{}", *ptr); // 未定义行为!
    }
}

错误3:违反别名规则

fn alias_violation() {
    let mut data = 42;
    let ref1 = &data;
    let ref2 = &mut data; // 错误:同时存在可变和不可变引用
    
    println!("{}", ref1);
}

总结对比表

特性 &i32 *const i32 *mut i32
安全性 安全 不安全 不安全
空值 不可能 可能 可能
可变性 不可变 不可变 可变
别名检查 严格
生命周期 编译器验证 无保证 无保证
使用场景 常规编程 FFI/只读访问 FFI/写访问
解引用 安全 unsafe unsafe
自动转换 支持 Deref 不支持 不支持

在 Rust 开发中,优先使用引用 &i32,只有在必要时(如 FFI 或底层系统编程)才使用裸指针 *i32,并且始终将其封装在安全的抽象中。


网站公告

今日签到

点亮在社区的每一天
去签到