一、从一道算法题开始
题目:给定一个链表 1 -> 2 -> 3 -> 4,返回 2 -> 1 -> 4 -> 3。
// Definition for singly-linked list. // #[derive(PartialEq, Eq, Clone, Debug)] // pub struct ListNode { // pub val: i32, // pub next: Option<Box<ListNode>> // } // // impl ListNode { // #[inline] // fn new(val: i32) -> Self { // ListNode { // next: None, // val // } // } // } impl Solution { pub fn swap_pairs(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> { if head.is_none() { return head; } let mut dummy = Box::new(ListNode{val:0,next:head}); let mut curr = dummy.as_mut(); while let Some(mut first_node) = curr.next.take() { if let Some(mut second) = first_node.next.take() { // swap curr.next = Some(sec_node); first_node.next = sec_node.next; sec_node.next = Some(first_node); curr = first_node.as_mut(); } else { curr.next = Some(first_node); break; } } dummy.next } }
这道题是编译不过的。存在以下问题:
- 1、
curr.next.take()导致所有权丢失
循环中 curr.next.take() 拿走了 curr.next 的所有权,但后面又希望通过 curr 继续修改链表结构,这时 curr 的内部结构已经被部分“拆走”,再使用它会出现逻辑错误或借用冲突。
-
2、
curr = first_node.as_mut()错误first_node在当前作用域结束时会被 drop(销毁),但curr设成了first_node.as_mut()的引用。 ——这会导致 悬垂引用 (dangling reference)。 -
3、缺少
&mut链接回去 你改变了curr.next,但是curr本身的可变借用和后续更新没有正确维持链表连接。
比较推荐的做法是这样
pub fn swap_pairs(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> { let mut dummy = Box::new(ListNode { val: 0, next: head }); let mut curr = dummy.as_mut(); while let Some(mut first) = curr.next.take() { // take() 拿走所有权 if let Some(mut second) = first.next.take() { // 拿走第二个节点 // 交换 first.next = second.next.take(); second.next = Some(first); curr.next = Some(second); // 移动 curr 指针 curr = curr.next.as_mut().unwrap().next.as_mut().unwrap(); ✅ } else { curr.next = Some(first); break; } } dummy.next }
这样写有几个疑问:
- 1、Box &mut dummy 与 dummy.as_mut()有什么区别
- 2、Option + Box的as_ref与as_mut有什么区别
- 3、为什么拿到
&mut Box<ListNode>可变引用之后,**不能直接赋值(=Some(...))?**明明它是“可变”的啊? - 4、匹配规则:Some(mut first) 可以改为Some(ref mut first)吗
二、问题分析
1、Box &mut dummy 与 dummy.as_mut()有什么区别?
a、先看类型对比:
| 表达式 | 类型 | 含义 |
|---|---|---|
&mut dummy | &mut Box<ListNode> | 对整个 Box 的可变借用(指向“盒子”本身) |
dummy.as_mut() | &mut ListNode | 对盒子里 内部数据(ListNode) 的可变借用 |
b、从内存结构看:
&mut dummy拿到的是指向整个 Box 的引用,
&mut dummy ───► (Box<ListNode>)
能修改“dummy”这个变量本身,比如让它重新指向别的 Box,但不能直接访问 ListNode 的字段。
let r1 = &mut dummy; // r1.next ❌ 编译错误 // (*r1).next ✅ 需要解引用
dummy.as_mut()拿到的是指向 Box 里内容(ListNode)的引用:
dummy.as_mut() ───► [ListNode { val: 0, next: None }]
以直接访问链表节点的字段(val, next),但不能替换整个 Box。
let r2 = dummy.as_mut(); r2.val = 10; // ✅ 修改节点的值 r2.next = Some(...); // ✅ 修改 next 指针 // r2 = &mut Box::new(...) ❌ 不行
c、扩展到另一个问题:
let mut curr= &mut Box...与let curr = &mut Box mut去掉后还能用吗?
先说类型推导
let dummy = Box::new(ListNode { val: 0, next: head }); let curr = &mut dummy; // 或 let mut curr = &mut dummy;
Rust 会自动推导 curr 为不可变绑定(immutable binding),即 curr 这个变量本身不能被重新绑定。
| 代码 | 是否可行 | 说明 |
|---|---|---|
curr = &mut node.next; | ❌ 如果 curr 没加 mut | 绑定是不可变的,不能重新赋值 |
curr.next = Some(Box::new(...)); | ✅ 可以 | 可变引用允许修改引用指向对象的内容 |
let first = curr.next.take(); | ✅ 可以 | 可变引用允许 move 内部字段 |
curr = &mut dummy;(重新绑定) | ❌ 如果 curr 没加 mut | 不可变绑定不能重新赋值 |
- 可变绑定(mut) 控制的是变量
curr本身能否被重新赋值 - &mut 引用 控制的是它指向的内容能否被修改
c、简单理解
&mut dummy:拿到“箱子”的引用。
dummy.as_mut():打开箱子,拿到“里面的东西”的引用。
2、Box加Option as_ref与as_mut区别
- 先看对Option的调用
| 调用 | 输入类型 | 返回类型 | 含义 |
|---|---|---|---|
option.as_ref() | Option<Box<T>> | Option<&Box<T>> | 不可变借用 Box(Option 层不变) |
option.as_mut() | Option<Box<T>> | Option<&mut Box<T>> | 可变借用 Box(Option 层不变) |
- 对
Box<T>调用
| 调用 | 输入类型 | 返回类型 | 含义 |
|---|---|---|---|
box.as_ref() | Box<T> | &T | 拿到堆中数据的不可变引用 |
box.as_mut() | Box<T> | &mut T | 拿到堆中数据的可变引用 |
把两者加起来,汇总如下:
let mut node = Some(Box::new(ListNode { val: 1, next: None }));
| 表达式 | 类型 | 说明 |
|---|---|---|
node | Option<Box<ListNode>> | 拥有或为空 |
node.as_ref() | Option<&Box<ListNode>> | 借用外层 Box(不可变) |
node.as_ref().unwrap() | &Box<ListNode> | 拿到 Box 的引用 |
node.as_ref().unwrap().as_ref() | &ListNode | 再打开 Box,借用内部数据 |
node.as_mut() | Option<&mut Box<ListNode>> | 可变借用 Box |
node.as_mut().unwrap() | &mut Box<ListNode> | Box 的可变引用 |
node.as_mut().unwrap().as_mut() | &mut ListNode | 可变借用 Box 内部节点 |
3、为什么拿到 &mut Box<ListNode> 可变引用之后,**不能直接赋值(=Some(...))?**明明它是“可变”的啊?
let mut curr: &mut Box<ListNode> = ...; curr.next.as_mut(); // 得到 Option<&mut Box<ListNode>> curr.next = Some(Box::new(ListNode::new(5))); // ❌ 报错
-
明确一个原则Rust 的**“可变引用” ≠ “可替换所有权”**
-
&mut T表示“对 T 的独占访问权”,即:你可以通过它去修改 T 内部的字段。 -
但它 不是拥有者,它只是“借用者(borrower)”。
可以这样操作:
curr.next.take(); // ✅ 修改 curr.next 内部字段 curr.next = Some(...); // ✅ 只要 curr 是 “持有所有权者”但你不能:
curr = Some(...); // ❌ 因为 curr 是引用(借来的) -
-
区分“修改内容”和“替换绑定”
可以“通过引用修改内容”,但不能“修改引用指向谁”。如下面的例子:
struct Node { val: i32 } let mut a = Node { val: 1 }; let mut b = Node { val: 2 }; let r: &mut Node = &mut a; // r 指向 a r.val = 10; // ✅ 修改 a 的内容 r = &mut b; // ❌ Rust 不允许重绑定 &mut 引用 -
与问题2组合对比如下:
| 类型 | 可修改内容 | 可改变指向 | 是否拥有所有权 | 常见用途 |
|---|---|---|---|---|
Box<T> | ✅ | ✅ | ✅ | 堆上所有权 |
&mut T | ✅ | ❌ | ❌ | 独占借用 |
&T | ❌ | ❌ | ❌ | 共享只读借用 |
Option<Box<T>> | ✅(take, replace) | ✅(Some/None) | ✅ | 所有权容器 |
4、if Some(ref mut)与 if Some(mut)这是怎么匹配的
Some(mut node)—— 拿走了所有权
let mut head = Some(Box::new(ListNode { val: 1, next: None })); while let Some(mut node) = head { node.val += 1; head = node.next; // ✅ 因为我们拥有 node,所以能移动出它的 next }
head变成None, 可以自由地操作Node, node.next、替换、拼接等。
Some(ref mut node)—— 借用
let mut head = Some(Box::new(ListNode { val: 1, next: None })); let mut current = &mut head; while let Some(ref mut node) = current { node.val += 1; current = &mut node.next; // ✅ 仅可变借用,不消耗 head }
不移动所有权,可沿着链表前进而不破坏原结构。
因为只是借用,Rust 不允许你把内部值 move 出来( 不能node.next.take())。
-
ref mut node 虽然是借用,但是可以修改next吗
可以的,这样操作:
while let Some(ref mut node) = current { // 增加值 node.val += 1; // 跳过下一个节点 if let Some(skip_node) = node.next.as_mut().and_then(|n| n.next.take()) { node.next = Some(skip_node); } // 移动 current current = &mut node.next; }
三、小结
稍微总结下。Option 层只是“包裹”,不会自动解引用。Box 层是“智能指针”,会自动解引用一次。访问字段会自动解引用生效(box.val 合法)。访问 Option先 unwrap 或 as_mut() 再继续。
1198

被折叠的 条评论
为什么被折叠?



