当前位置: 首页 > news >正文

不可变借用的规则与限制: 从只读语义到零拷贝架构的 5 000 字深潜

在这里插入图片描述


0. 引言:为什么不可变借用是 Rust 并发的“第一公民”?

在 Rust 里,不可变借用 &T 同时承担了 两件事

  1. 只读访问:禁止通过该引用修改目标数据。
  2. 共享并发任意多个 &T 可以同时存在,只要没有 &mut T

这条规则看似简单,却直接决定了:

  • 能否 零拷贝 解析网络包
  • 能否 并行迭代 rayon
  • 能否 lock-free 读取全局配置
  • 能否 安全地 暴露底层切片给 C

本文将:

  1. 词法 & 数据流 两个维度拆解不可变借用的 规则与限制
  2. 200+ 行代码 实现一个 零拷贝 JSON DOM + 并行查询
  3. 给出 常见误用清单高级技巧UnsafeCell、内部可变性、PhantomData

1. 不可变借用的 3 条核心规则

维度规则违反后果
别名规则同一时刻可以存在 N 个 &T1 个 &mut T,但不能混用E0502
变异规则通过 &T 不能获得 &mut TE0596
生命周期规则不可变借用的 Region 必须 覆盖所有使用点E0597

所有规则均在 编译期 由 borrow checker 在 MIR 层证明。


2. 编译器视角:Region + Loan + Access

2.1 MIR 数据流示例

fn demo(slice: &[i32]) -> i32 {let r1 = &slice[0]; // Loan₁ (shared)let r2 = &slice[1]; // Loan₂ (shared)*r1 + *r2
}

MIR 简化:

bb0:_2 = &(*_1)[0 of 2]; // Loan₁_3 = &(*_1)[1 of 2]; // Loan₂_4 = (*_2) + (*_3);
  • Loan₁Loan₂ 均为 共享 → 合法
  • Region 计算到 _4 为止 → borrow checker 通过 ✅

2.2 违反示例:共享与可变混用

fn conflict(v: &mut Vec<i32>) {let r = &v[0];   // Loanₛ(v[0..4])v.push(4);       // Loanₘ(v[0..cap]) → 冲突 ❌println!("{}", r);
}

3. 不可变借用的 4 大限制

3.1 限制 1:不能升级成可变

fn cannot_upgrade(x: &i32) -> &mut i32 {&mut *x // ❌ E0596
}

3.2 限制 2:不能内部可变(默认)

use std::cell::Cell;
fn cell_demo(c: &Cell<i32>) {c.set(42); // ✅ Cell 内部可变性let r = &c.get(); // 不可变借用 Cell<i32>// *r = 99; // ❌ 不能修改内部值
}

不可变借用 默认 不可变,除非容器显式使用 UnsafeCell

3.3 限制 3:不能跨越悬垂生命周期

fn dangling() -> &'static str {let s = String::from("hello");&s // ❌ E0515
}

3.4 限制 4:不能逃逸出所有者

struct Buffer<'a> {data: &'a [u8],
}impl<'a> Buffer<'a> {fn slice(&self) -> &'a [u8] {self.data // 合法:生命周期已连接}
}

4. 深度实战:零拷贝 JSON DOM + 并行查询

目标:

  • 解析 JSON 字符串 → 不复制字节
  • 构建 DOM → 所有字符串节点都是 &'input str
  • 并行遍历 → rayon 迭代器
  • 零 unsafe,100 % safe Rust

4.1 AST 设计

#[derive(Debug)]
pub enum Node<'a> {Null,Bool(bool),Num(&'a str),Str(&'a str),Arr(Vec<Node<'a>>),Obj(Vec<(&'a str, Node<'a>)>),
}
  • 生命周期 'a输入字节切片 绑定
  • 所有字符串节点都是 不可变借用,因此可以 任意共享 给多线程

4.2 解析器

use std::str::CharIndices;struct Parser<'a> {src: &'a str,chars: CharIndices<'a>,
}impl<'a> Parser<'a> {fn new(src: &'a str) -> Self {Self { src, chars: src.char_indices() }}fn parse(&mut self) -> Node<'a> {self.skip_ws();self.parse_value()}fn parse_value(&mut self) -> Node<'a> {match self.peek() {Some(b'n') => { self.consume("null"); Node::Null }Some(b't') => { self.consume("true"); Node::Bool(true) }Some(b'f') => { self.consume("false"); Node::Bool(false) }Some(b'"') => Node::Str(self.parse_str()),Some(b'[') => self.parse_arr(),Some(b'{') => self.parse_obj(),_ => Node::Num(self.parse_num()),}}fn parse_str(&mut self) -> &'a str {let start = self.chars.next().unwrap().0; // skip '"'while let Some((idx, ch)) = self.chars.next() {if ch == '"' {return &self.src[start + 1..idx];}}panic!("unterminated string");}fn parse_num(&mut self) -> &'a str {let start = self.chars.as_str().as_ptr() as usize - self.src.as_ptr() as usize;while let Some(ch) = self.peek() {if ch.is_ascii_digit() || ch == b'.' || ch == b'-' {self.chars.next();} else {break;}}let end = self.chars.as_str().as_ptr() as usize - self.src.as_ptr() as usize;&self.src[start..end]}fn parse_arr(&mut self) -> Node<'a> {self.chars.next(); // skip '['let mut out = Vec::new();self.skip_ws();if self.peek() == Some(b']') {self.chars.next();return Node::Arr(out);}loop {out.push(self.parse_value());self.skip_ws();match self.peek() {Some(b']') => { self.chars.next(); break; }Some(b',') => { self.chars.next(); self.skip_ws(); }_ => panic!("expected ',' or ']'"),}}Node::Arr(out)}fn parse_obj(&mut self) -> Node<'a> {self.chars.next(); // skip '{'let mut out = Vec::new();self.skip_ws();if self.peek() == Some(b'}') {self.chars.next();return Node::Obj(out);}loop {let key = self.parse_str();self.skip_ws();assert_eq!(self.chars.next().unwrap().1, ':');self.skip_ws();let val = self.parse_value();out.push((key, val));self.skip_ws();match self.peek() {Some(b'}') => { self.chars.next(); break; }Some(b',') => { self.chars.next(); self.skip_ws(); }_ => panic!("expected ',' or '}'"),}}Node::Obj(out)}fn skip_ws(&mut self) {while let Some(ch) = self.peek() {if ch.is_ascii_whitespace() {self.chars.next();} else {break;}}}fn peek(&mut self) -> Option<u8> {self.chars.clone().next().map(|(_, ch)| ch as u8)}fn consume(&mut self, s: &str) {for expected in s.bytes() {let actual = self.chars.next().unwrap().1 as u8;assert_eq!(actual, expected);}}
}

4.3 并行查询

use rayon::prelude::*;impl<'a> Node<'a> {fn find_strings(&self) -> Vec<&'a str> {match self {Node::Str(s) => vec![*s],Node::Arr(children) => children.par_iter().flat_map(|n| n.find_strings()).collect(),Node::Obj(entries) => entries.par_iter().flat_map(|(_, n)| n.find_strings()).collect(),_ => vec![],}}
}
  • rayonpar_iter 需要 &'a str: Sync,而 不可变借用天生满足
  • 无需任何锁,即可 并行遍历 100 MB JSON

5. 常见误用清单 & 修复

误用现象修复
返回局部字符串&'a str 指向 String使用 Cow<'a, str>
误把 &T&mut TE0596引入 Cell<T>RefCell<T>
多线程写共享数据数据竞争使用 Arc<Mutex<T>>
跨 FFI 返回切片悬垂使用 借用守卫 RAII

6. 高级技巧:PhantomData 与零开销标记

当你需要 额外生命周期 但不占空间时:

use std::marker::PhantomData;struct Slice<'a> {ptr: *const u8,len: usize,_marker: PhantomData<&'a [u8]>,
}
  • PhantomData 参与借用检查,但 零大小
  • 用于 FFI自引用结构 的标记

7. 结语:不可变借用 = 并发友好的“只读契约”

  • 编译期:borrow checker 用 Region 证明 只读 + 共享 的合法性
  • 运行期:不可变借用天然 Sync,允许 零拷贝 + 并行
  • 工程实践:善用生命周期、PhantomData、Cow,无需 unsafe 即可获得 C 级性能

愿你在下一次写解析器、网络库或并发框架时,把不可变借用当成 最锋利的工具
在这里插入图片描述

http://www.dtcms.com/a/549566.html

相关文章:

  • 专题三 之 【二分查找】
  • C++进阶: override和final说明符-----继承2中重写的确认官和刹车(制动器)
  • 数据科学每日总结--Day7--数据库
  • opencv 学习: 01 ubuntu20.04 下 opencv 4.12.0 源码编译
  • 满足“国六”标准的通用型故障诊断仪:Q-OBD
  • 上海专业建站公湖南网站建设设计
  • 智慧时空大数据平台:释放时空信息数据价值
  • 线程基本概念
  • MySQL MDL锁阻塞DDL 导致复制线程卡住
  • 智慧管理,赋能美容院新未来
  • Flink做checkpoint迟迟过不去的临时解决思路
  • 网站注册 优帮云wordpress首页静态化
  • [人工智能-大模型-115]:模型层 - 用通俗易懂的语言,阐述神经网络为啥需要多层
  • Actix Web 不是 Nginx:解析 Rust 应用服务器与传统 Web 服务器的本质区别
  • pdf文件上传下载记录
  • 辽阳网站设计中国建设银行的网站.
  • 2. WPF程序打包成一个单独的exe文件
  • 东软专业力考试--Java Web 开发基础
  • 8方向控制圆盘View
  • js中Map和对象{}的区别
  • 基于python构建的低温胁迫实验
  • 服装公司网站修改wordpress后台登陆
  • 2025 Avalonia 技术全景:从版本迭代到生产级落地的成熟之路
  • 做网站却不给客户源代码奥迪互动平台
  • python基础一
  • Burp Suite 代理切换插件
  • 怎么做企业网站推广网站推广方案
  • Jaccard相似度:集合相似性的经典度量
  • 十七、STM32的TIM(八)(TIM输入捕获)
  • c语言笔记 格式化输出函数的使用