Rust 核心概念解析:引用、借用与内部可变性

Rust 核心概念解析:引用、借用与内部可变性

管理内存安全,特别是防止数据竞争和悬垂指针,是系统编程中的一个核心挑战。Rust 语言通过其所有权和借用检查系统,在编译阶段就为解决这些问题提供了强有力的保障。

本文聚焦于该系统的关键部分:引用。我们将详细解析共享引用 (&T) 与可变引用 (&mut T) 的工作原理与编译时规则,并进一步探讨"内部可变性"这一高级模式,它为特定场景提供了必要的灵活性。

引用及内部可变性

引用

  • 通过引用,Rust 允许将值借用出去,但不放弃所有权
  • 引用就是带有附加合约的指针

共享引用

  • &T,就是可以共享的指针:
    • 可同时存在任意数量的引用指向同一个值
    • 每个共享的引用都实现了 Copy
  • 背后的值不可变
    • 编译器允许假定共享引用指向的值,在该引用存活期间是不会改变的
    • 例如:一个共享引用的值在某函数内被多次读取,那编译器就有权让其只读取一次,然后重用读取的值。

可变引用

  • &mut T
  • 可变引用是独占的
    • 编译器假定没有其它线程访问目标值(无论是通过共享引用还是可变引用)
rust 复制代码
fn main() {
  println!("Hello, world!");
}

fn noalias(input: &i32, output: &mut i32) {
  if *input == 1 {
    *output = 2;
  }
  if  *input !=1 {
    *output = 3;
  }
}
  • 可变引用只允许你修改引用所指向的内存地址
rust 复制代码
fn main() {
  let x = 42;
  let mut y = &x; // y is of type &i32
  let z = &mut y; // z is of type &mut &i32
}

拥有值 VS 拥有到值的可变引用

  • 所有者需要对删除值(丢弃值)负责
  • 警告:如果你移动了可变引用背后的值,则必须在其位置上留下另一个值。如果不这样做,所有者会认为他需要将其删除(丢弃),但其实却没有值可以删除了。
rust 复制代码
fn main() {
  let mut s = Box::new(42);
  replace_with_84(&mut s);
}

fn replace_with_84(s: &mut Box<i32>) {
  // this is not okay, as *s would be empty;
  // let was = *s;
  // but this is:
  let was = std::mem::take(s);
  // so is this:
  *s = was;
  // we can exchange values behind &mut:
  let mut r = Box::new(84);
  std::mem::swap(s, &mut r);
  assert_ne!(*r, 84);
}

内部可变性

  • 一些类型提供了内部可变性:
    • 可通过共享引用修改值
  • 这些类型通常依赖于额外的机制(如原子 CPU 指令)或不变量来提供安全的可变性,而不依赖于独占引用的语义
  • 分为两类:
    • 通过共享引用获得可变引用
    • 通过共享引用可以替换值
  • 通过共享引用获得可变引用:Mutex、RefCell
    • 提供保障机制:针对任何提供了可变引用指向的值,同时只会存在一个可变引用(没有共享引用)
    • 依赖于 UnsafeCell 类型,通过共享引用修改值的唯一正确方式
  • 通过共享引用可以替换值:std::sync::atomic、std::cell::Cell
    • 没有提供可变引用到内部值
    • 提供就地操作值的方法
    • 例:无法获得到 usize 或 i32 的直接引用,但是可以读取和替换值

Cell 类型

  • 标准库
  • 通过不变量实现的内部可变性
    • 无法跨线程共享(内部值不会被并发的修改,即使通过共享引用发生修改)
    • 不会提供到 Cell 内部的值的引用(所以可以一直移动它)
  • 提供的方法:
    • 对值整体替换
    • 返回值的副本

总结

本文的核心在于 Rust 的引用系统,其安全性建立在两条基本原则之上:数据可以在多个共享引用 (&T) 之间只读共享,或者通过一个独占的可变引用 (&mut T) 进行修改。编译器的借用检查器严格实施这些规则,从而有效防止了数据竞争。

然而,为了在不牺牲安全的前提下提高灵活性,Rust 引入了"内部可变性"作为补充机制。像 Cell 等类型,将借用规则的检查从编译时推迟到运行时,允许在共享引用的访问范围内安全地修改数据。

因此,Rust 的内存安全模型是编译时静态检查和运行时动态检查的结合。理解这两种机制如何协同工作,是编写出高效且健壮的 Rust 代码的重要基础。

参考

相关推荐
申君健24864182772022 小时前
AI SDK 概念全景总结(v7)
后端
imDwAaY2 小时前
SQL 查询突然变慢了?一篇讲清慢 SQL 排查思路
后端·mysql
小羊没烦恼!3 小时前
T-SQL查询高级—SQL Server索引中的碎片和填充因子
开发语言·前端·javascript·数据库·后端·python·web安全
the局外人3 小时前
学会 SQLAlchemy 搭建 Python 与 MySQL 之间的数据访问桥梁 🚀
后端·python·sql
金銀銅鐵3 小时前
[Java] 用 GUI 展示 ArrayList 扩容的过程
java·后端·ai编程
霸道流氓气质4 小时前
基于 Spring @Profile 的 MQ 消费者按需开关设计
java·后端·spring
逆风优雅4 小时前
nestjs middleware 接口请求日志
后端·js
是枚小菜鸡儿吖4 小时前
听歌截图太普通?我把MusicCard部署到极空间,把歌曲做成随时可开的音乐海报页
服务器·人工智能·后端·ai·大模型