惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
罗磊的独立博客
V
Visual Studio Blog
爱范儿
爱范儿
H
Help Net Security
J
Java Code Geeks
I
InfoQ
Recent Announcements
Recent Announcements
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Recorded Future
Recorded Future
Jina AI
Jina AI
Microsoft Security Blog
Microsoft Security Blog
WordPress大学
WordPress大学
GbyAI
GbyAI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
Scott Helme
Scott Helme
S
SegmentFault 最新的问题
S
Securelist
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss
博客园_首页
B
Blog
F
Fortinet All Blogs
AWS News Blog
AWS News Blog
V
Vulnerabilities – Threatpost
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Forbes - Security
Forbes - Security
S
Security @ Cisco Blogs
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Schneier on Security
Project Zero
Project Zero
Martin Fowler
Martin Fowler
C
Cybersecurity and Infrastructure Security Agency CISA
N
Netflix TechBlog - Medium
N
News and Events Feed by Topic

博客园 - PKICA

git实用命令 rust底层设计理念值得注意的几个地方总结 Rust内存重解释transmute C与Rust类型映射 Rust FFI 安全抽象范式 rust延迟初始化原语 rust重借用机制与原理 rust参数传递模型 汇编语言语法详解 gdb汇编调试 gdb-pwndbg的安装与使用指南 gdb调试插件gef C语言thread_local linux系统readelf命令使用指南 gcore转储进程内存 gdb查看命令 RGB与YUV颜色编码的区别 Rust原子类型 C++ STL求两个集合交集差集 gdb调试集锦 ubuntu24.0.4使用root用户登录 ubuntu24.0.4输入密码后跳回登录界面 AI内存压缩技术TurboQuant及存疑 ubuntu切换到指定内核版本 在没有顶级科技大佬直接背书的情况下deepseek为啥能够异军突起? HuggingFace和deepseek的关系 当前主流AI大模型 Rust写时克隆Cow系列2
rust可变引用作为函数参数的机理详解
PKICA · 2026-06-06 · via 博客园 - PKICA

本文其实是之前rust重借用机制与原理的细化,如果已经理解了这篇文章,可以不用再看这篇文章。

为了彻底搞懂这个问题,我们需要抛开具体的复杂业务代码,建立两个最核心的 Rust 底层世界观:

  • 引用也是一个普通的值。 它和 StringVec 没有本质区别,在内存里也占字节,也有自己的所有权
  • &mut T(可变引用)是独占的,绝对不允许被复制(Non-Copy)。

下面我们用最纯粹、最简单的抽象例子,一步步拆解“为什么可变引用直接传会触发 Move”。

1.理解为什么 &mut T 会触发 Move

在 Rust 中,所有类型在面临“赋值”或“传参”时,只有两条路可以走:要么执行 Copy(复制),要么执行 Move(移动)。

  • 如果是共享引用 &T:它实现了 Copy。因为多个只读指针指向同一个数据是绝对安全的。
  • 如果是可变引用 &mut T:它没有实现 Copy。因为 Rust 的安全基石是 “独占可变性”(同一时间只能有一个活跃的写指针)。如果 &mut T 允许被 Copy,意味着你可以完美克隆出两个一模一样的指针同时修改同一块内存,这会导致数据竞争。

因为 &mut T 不能被 Copy,所以当你把它当成一个普通变量直接传递时,它只能执行 Move。

抽象实验:

fn main() {
    let mut data = 42;
    let r1: &mut i32 = &mut data; // r1 拥有这个可变引用的所有权

    let r2 = r1; // 💥 触发了 Move!r1 的所有权转移给了 r2

    // println!("{}", r1); // ❌ 编译报错:使用了已被移动的值(use of moved value)
    println!("{}", r2); // ✅ 正常,现在 r2 是唯一合法的可变引用
}

2.为什么在函数调用时,我们很少感知到这个 Move?

既然直接传会触发 Move,那为什么我们在写普通 Rust 代码时,连续把一个可变引用传给两个函数,编译器却不报错呢?

fn print_mut(r: &mut i32) { /* ... */ }

fn foo(mut_ref: &mut i32) {
    print_mut(mut_ref); // 👈 看起来是直接传,这里为什么没被 Move?
    print_mut(mut_ref); // 👈 如果上面 Move 了,这里为什么还能用?
}

答案是:编译器在暗中帮我们做了一次“偷梁换柱”,这个机制叫 隐式再借用(Implicit Reborrow)。

当编译器看到 print_mut(mut_ref),并且发现你后面还要继续使用 mut_ref 时,它为了不让你写代码太痛苦,会悄悄把你的代码改写成:

print_mut(&mut *mut_ref); // 👈 编译器自动做的“再借用”
  • *mut_ref:先解引用,找到底层真正的数据。
  • &mut ...:对底层数据重新生成一个短命的、临时的可变引用。

在这个过程中,mut_ref 本身的所有权牢牢留在了 foo 函数里。借出去的只是一个临时的“影子引用的所有权”,等 print_mut 执行完,这个影子消失,mut_ref 自动复活。

3.什么时候隐式再借用会失效?(也就是为什么原代码要加 &mut

既然编译器这么聪明,为什么在某些特定的代码结构(比如函数参数: mut rjson: &mut RapidJson)中,直接传 rjson 会触发 Move,甚至逼得原作者必须手动写 &mut rjson 呢?

有以下几个经典场景会破坏编译器的聪明才智,导致隐式再借用失效,强行触发 Move:

场景 1:变量本身被 mut 修饰(指针重新绑定)

如果参数声明写成了 mut rjson: &mut T(注意变量名前面有 mut):
这告诉编译器:“我接下来不仅要用这个指针,我还可能在当前函数里把这个指针本身改指向别的地方(例如 rjson = &mut other_data;)。”
一旦变量本身是可变的,Rust 编译器在推导生命周期和再借用时就会变得极度保守。它为了确保万无一失,往往会直接放弃隐式再借用,将其视作纯粹的 Move。

场景 2:泛型或 FFI 边界的复杂包装

在涉及 extern "C" 跨语言边界、非标准的泛型约束、或者多重闭包(Closure)时,Rust 的生命周期分析器(Borrow Checker)无法完美穿透复杂的上下文。一旦它无法确定后续是否安全,就会立刻退化到最基础的语义:&mut T 没有 Copy 特性,一律按 Move 处理。

第四步:手动加上 &mut rjson 到底做了什么?

当隐式再借用因为上述原因失效,而你直接传 rjson 又会被 Move 导致后续无法使用时,手动加上 &mut rjson 成了一种强行的破局手段。

如果 rjson 的类型是 &mut T

  • 不加 &mut(直接写 rjson):你传递的是 &mut T 本身。因为隐式再借用失效,它发生 Move,rjson 变量从此死亡。
  • 加上 &mut rjson:你创建了一个全新的指针,类型是 &mut &mut T指向可变引用的可变引用)。
    你把这个新创建的双重指针作为值传进了下一个函数。在这个过程中,作为基石的 rjson 本身没有任何所有权转移,它安全地留在当前函数中。

虽然接收方函数可能只需要 &mut T,但 Rust 面对 &mut &mut T 传入 &mut T 的参数要求时,会触发标准的 Deref(解引用)自动降级,将其剥离为单层引用成功匹配,同时完美保住了 rjson 的所有权。

总结核心逻辑

  • rjson 本身确实是可变引用。
  • 但可变引用在 Rust 中是独占且不可复制的(Non-Copy)。
  • 纯 Rust 代码下能直接传,全靠编译器在背后偷偷做 &mut *rjson(再借用)。
  • 一旦代码复杂(如变量自身可变、FFI 边界),编译器的偷懒机制失效,直接传就变成了不可逆的 Move。
  • 手动加 &mut rjson 是通过显式创建一个临时的“二级指针”,强行把底层 rjson 的所有权锁在当前函数里。

参考资料:

1.rust重借用机制与原理