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

这是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)"。

    可以这样操作:

    ruby 复制代码
    curr.next.take();          // ✅ 修改 curr.next 内部字段
    curr.next = Some(...);     // ✅ 只要 curr 是 "持有所有权者"

    但你不能:

    ini 复制代码
    curr = Some(...);          // ❌ 因为 curr 是引用(借来的)
  • 区分"修改内容"和"替换绑定"

    可以"通过引用修改内容",但不能"修改引用指向谁"。如下面的例子:

    ini 复制代码
    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) ------ 拿走了所有权
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吗

    可以的,这样操作:

    rust 复制代码
    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() 再继续。

如果觉得有用,请点下关注吧😊,本人公众号大鱼七成饱

相关推荐
倔强的石头_2 小时前
【征文计划】Rokid 语音唤醒技术深度解析:从声学模型到低功耗优化实践
后端
吾疾唯君医3 小时前
记录GoLang创建文件并写入文件的中文乱码错误!
开发语言·后端·golang
LucianaiB3 小时前
【征文计划】用 Rokid AR 眼镜做“厨房小助手”:让做饭不再手忙脚乱
后端
数据知道3 小时前
Go基础:Go语言ORM框架GORM详解
开发语言·jvm·后端·golang·go语言
Pr Young4 小时前
go build命令
后端·golang
数据知道4 小时前
Go语言:Go 语言中的命令行参数操作详解
开发语言·后端·golang·go语言
代码匠心5 小时前
从零开始学Flink:实时流处理实战
java·大数据·后端·flink
hui函数5 小时前
Python全栈(基础篇)——Day05:后端内容(dict与set+while循环+for循环+实战演示+每日一题)
开发语言·后端·python