这是Rust九九八十一难的第六篇。之前写了&和Box指针的使用入门,但是碰到Option的时候还是有点懵。比如Option<Box>,option的as_mut与Box的as_mut怎么配合使用,他们是什么关系;if Some(ref mut)与 if Some(mut)各是什么使用场景。今天统一梳理下。
一、从一道算法题开始
题目:给定一个链表 1 -> 2 -> 3 -> 4
,返回 2 -> 1 -> 4 -> 3
。
rust
// 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
本身的可变借用和后续更新没有正确维持链表连接。
比较推荐的做法是这样
rust
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 的引用,
rust
&mut dummy ───► (Box<ListNode>)
能修改"dummy"这个变量本身,比如让它重新指向别的 Box,但不能直接访问 ListNode 的字段。
rust
let r1 = &mut dummy;
// r1.next ❌ 编译错误
// (*r1).next ✅ 需要解引用
dummy.as_mut()
拿到的是指向 Box 里内容(ListNode)的引用:
python
dummy.as_mut() ───► [ListNode { val: 0, next: None }]
以直接访问链表节点的字段(val
, next
),但不能替换整个 Box。
ini
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去掉后还能用吗?
先说类型推导
ini
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 |
拿到堆中数据的可变引用 |
把两者加起来,汇总如下:
less
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(...)
)?**明明它是"可变"的啊?
rust
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)"。
可以这样操作:
rubycurr.next.take(); // ✅ 修改 curr.next 内部字段 curr.next = Some(...); // ✅ 只要 curr 是 "持有所有权者"
但你不能:
inicurr = Some(...); // ❌ 因为 curr 是引用(借来的)
-
-
区分"修改内容"和"替换绑定"
可以"通过引用修改内容",但不能"修改引用指向谁"。如下面的例子:
inistruct 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)
------ 拿走了所有权
rust
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)
------ 借用
rust
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吗
可以的,这样操作:
rustwhile 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() 再继续。
如果觉得有用,请点下关注吧😊,本人公众号大鱼七成饱。