Rust的Option碰到指针Box:数据怎么解

一、从一道算法题开始

题目:给定一个链表 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 }]

以直接访问链表节点的字段(valnext),但不能替换整个 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 }));

表达式类型说明
nodeOption<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() 再继续。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值