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 代码的重要基础。

参考

相关推荐
brzhang2 小时前
颠覆你对代码的认知:当程序和数据只剩下一棵树,能读懂这篇文章的人估计全球也不到 100 个人
前端·后端·架构
躲在云朵里`2 小时前
SpringBoot的介绍和项目搭建
java·spring boot·后端
喵个咪3 小时前
Golang微服框架Kratos与它的小伙伴系列 - 分布式事务框架 - DTM
后端·微服务·go
brzhang3 小时前
我见过了太多做智能音箱做成智障音箱的例子了,今天我就来说说如何做意图识别
前端·后端·架构
小蜗牛狂飙记3 小时前
在github上传python项目,然后在另外一台电脑下载下来后如何保障成功运行
开发语言·python·github
晴空月明4 小时前
结构型模式-架构解耦与扩展实践
后端
WanderInk4 小时前
在递归中为什么用 `int[]` 而不是 `int`?——揭秘 Java 参数传递的秘密
java·后端·算法
why技术4 小时前
哎,我糊涂啊!这个需求居然没想到用时间轮来解决。
java·后端·面试
鱼樱前端5 小时前
rust基础(一)
前端·rust