This is the sixth article in the series of Rust’s challenges. Previously, I wrote an introduction to using & and Box pointers, but I was still a bit confused when encountering Option. For example, <span>Option<Box<T>></span>, how do the as_mut methods of Option and Box work together, and what is their relationship? What are the use cases for if Some(ref mut) and if Some(mut)? Today, I will clarify these points.
1. Starting with an Algorithm Problem
Problem: Given a linked list <span>1 -> 2 -> 3 -> 4</span>, return <span>2 -> 1 -> 4 -> 3</span>.
// 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(second);
first_node.next = second.next;
second.next = Some(first_node);
curr = first_node.as_mut();
} else {
curr.next = Some(first_node);
break;
}
}
dummy.next
}
}
This problem will not compile due to the following issues:
1. <span>curr.next.take()</span> causes ownership loss.
In the loop, <span>curr.next.take()</span> takes ownership of <span>curr.next</span>, but later we want to continue modifying the linked list structure through <span>curr</span>. At this point, <span>curr</span>‘s internal structure has been partially “disassembled”, and using it again will lead to logical errors or borrowing conflicts.
2. <span>curr = first_node.as_mut()</span> is incorrect.
<span>first_node</span> will be dropped (destroyed) at the end of the current scope, but <span>curr</span> is set to a reference of <span>first_node.as_mut()</span>. This will lead to a **dangling reference**.
3. Missing <span>&mut</span> to link back.
You changed <span>curr.next</span>, but the mutable borrow of <span>curr</span> and subsequent updates did not correctly maintain the linked list connection.
A more recommended approach is to write it like this:
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() takes ownership
if let Some(mut second) = first.next.take() { // take the second node
// swap
first.next = second.next.take();
second.next = Some(first);
curr.next = Some(second);
// Move curr pointer
curr = curr.next.as_mut().unwrap().next.as_mut().unwrap(); ✅
} else {
curr.next = Some(first);
break;
}
}
dummy.next
}
There are several questions about this approach:
1. What is the difference between Box &mut dummy and dummy.as_mut()?
2. What is the difference between Option + Box’s as_ref and as_mut?
3. Why can’t I directly assign (<span>Some(...)</span><code><span>) after obtaining a mutable reference to </span><code><span>&mut Box<ListNode></span><span>? It is clearly "mutable"!</span>
4. Can the matching rule: Some(mut first) be changed to Some(ref mut first)?
2. Problem Analysis
1. What is the difference between Box &mut dummy and dummy.as_mut()?
a. Let’s look at the type comparison:
| Expression | Type | Meaning |
|---|---|---|
<span>&mut dummy</span> |
<span>&mut Box<ListNode></span> |
Mutable borrow of the entire <span>Box</span> (pointing to the “box” itself) |
<span>dummy.as_mut()</span> |
<span>&mut ListNode</span> |
Mutable borrow of the internal data (ListNode) inside the box |
b. From the perspective of memory structure:
<span>&mut dummy</span> gets a reference to the entire Box,
&mut dummy ───► (Box<ListNode>)
It can modify the “dummy” variable itself, such as pointing it to another Box, but cannot directly access the fields of ListNode.
let r1 = &mut dummy;
// r1.next ❌ Compilation error
// (*r1).next ✅ Need to dereference
<span>dummy.as_mut()</span> gets a reference to the content inside the Box (ListNode):
dummy.as_mut() ───► [ListNode { val: 0, next: None }]
It can directly access the fields of the linked list node (<span>val</span>, <span>next</span>), but cannot replace the entire Box.
let r2 = dummy.as_mut();
r2.val = 10; // ✅ Modify the node's value
r2.next = Some(...); // ✅ Modify the next pointer
// r2 = &mut Box::new(...) ❌ Not allowed
c. Extending to another question:
<span>let mut curr= &mut Box...</span> and <span>let curr = &mut Box</span> (removing mut) can it still be used?
First, let’s talk about type inference
let dummy = Box::new(ListNode { val: 0, next: head });
let curr = &mut dummy; // or let mut curr = &mut dummy;
Rust will automatically infer <span>curr</span> as an immutable binding, meaning that curr cannot be rebound.
| Code | Is it feasible? | Explanation |
|---|---|---|
<span>curr = &mut node.next;</span> |
❌ If <span>curr</span> does not have <span>mut</span> |
Binding is immutable, cannot reassign |
<span>curr.next = Some(Box::new(...));</span> |
✅ Yes | Mutable reference allows modification of the content of the referenced object |
<span>let first = curr.next.take();</span> |
✅ Yes | Mutable reference allows moving internal fields |
<span>curr = &mut dummy;</span> (rebinding) |
❌ If <span>curr</span> does not have <span>mut</span> |
Immutable binding cannot be reassigned |
- Mutable binding (mut) controls whether the variable
<span>curr</span>itself can be rebound - &mut reference controls whether the content it points to can be modified
d. Simple understanding
<span>&mut dummy</span>: Gets a reference to the “box”.
<span>dummy.as_mut()</span>: Opens the box, gets a reference to the “contents inside”.
2. The difference between Box and Option’s as_ref and as_mut
- First, let’s look at the calls to Option
| Call | Input Type | Return Type | Meaning |
|---|---|---|---|
<span>option.as_ref()</span> |
<span>Option<Box<T>></span> |
<span>Option<&Box<T>></span> |
Immutable borrow of Box (Option layer remains unchanged) |
<span>option.as_mut()</span> |
<span>Option<Box<T>></span> |
<span>Option<&mut Box<T>></span> |
Mutable borrow of Box (Option layer remains unchanged) |
- Calls on
<span>Box<T></span>
| Call | Input Type | Return Type | Meaning |
|---|---|---|---|
<span>box.as_ref()</span> |
<span>Box<T></span> |
<span>&T</span> |
Gets an immutable reference to the data on the heap |
<span>box.as_mut()</span> |
<span>Box<T></span> |
<span>&mut T</span> |
Gets a mutable reference to the data on the heap |
Combining both, we summarize as follows:
let mut node = Some(Box::new(ListNode { val: 1, next: None }));
| Expression | Type | Description |
|---|---|---|
<span>node</span> |
<span>Option<Box<ListNode>></span> |
Owned or None |
<span>node.as_ref()</span> |
<span>Option<&Box<ListNode>></span> |
Borrow the outer Box (immutable) |
<span>node.as_ref().unwrap()</span> |
<span>&Box<ListNode></span> |
Gets a reference to the Box |
<span>node.as_ref().unwrap().as_ref()</span> |
<span>&ListNode</span> |
Opens the Box, borrows the internal data |
<span>node.as_mut()</span> |
<span>Option<&mut Box<ListNode>></span> |
Mutable borrow of Box |
<span>node.as_mut().unwrap()</span> |
<span>&mut Box<ListNode></span> |
Mutable reference to the Box |
<span>node.as_mut().unwrap().as_mut()</span> |
<span>&mut ListNode</span> |
Mutable borrow of the internal node of the Box |
3. Why can’t I directly assign (<span>Some(...)</span>) after obtaining a mutable reference to <span>&mut Box<ListNode></span><span>? It is clearly "mutable"!</span>
let mut curr: &mut Box<ListNode> = ...;
curr.next.as_mut(); // Gets Option<&mut Box<ListNode>>
curr.next = Some(Box::new(ListNode::new(5))); // ❌ Error
-
Clarifying a principle: Rust’s “mutable reference” ≠ “replace ownership”
You can do this:
curr.next.take(); // ✅ Modify curr.next internal fields curr.next = Some(...); // ✅ As long as curr is the "owner"But you cannot:
curr = Some(...); // ❌ Because curr is a reference (borrowed) -
<span>&mut T</span>indicates “exclusive access” to T, meaning: you can modify the internal fields of T through it. -
But it is not the owner, it is just a “borrower”.
-
Distinguishing between “modifying content” and “replacing binding”.
You can “modify content through a reference”, but cannot “change who the reference points to”. For example:
struct Node { val: i32 } let mut a = Node { val: 1 }; let mut b = Node { val: 2 }; let r: &mut Node = &mut a; // r points to a r.val = 10; // ✅ Modify a's content r = &mut b; // ❌ Rust does not allow rebinding &mut reference -
Combining with the comparison from question 2:
| Type | Can modify content | Can change pointing | Does it own | Common usage |
|---|---|---|---|---|
<span>Box<T></span> |
✅ | ✅ | ✅ | Heap ownership |
<span>&mut T</span> |
✅ | ❌ | ❌ | Exclusive borrow |
<span>&T</span> |
❌ | ❌ | ❌ | Shared read-only borrow |
<span>Option<Box<T>></span> |
✅ (take, replace) | ✅ (Some/None) | ✅ | Ownership container |
4. How do if Some(ref mut) and if Some(mut) match?
<span>Some(mut node)</span>— Takes ownership
let mut head = Some(Box::new(ListNode { val: 1, next: None }));
while let Some(mut node) = head {
node.val += 1;
head = node.next; // ✅ Because we own node, we can move out its next
}
<span>head</span> becomes None, allowing free operations on Node, such as <span>node.next</span>, replacement, concatenation, etc.
<span>Some(ref mut node)</span>— Borrowing
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; // ✅ Only mutable borrow, does not consume head
}
Ownership is not moved, allowing traversal along the linked list without breaking the original structure.
Since it is only a borrow, Rust does not allow you to move the internal value out (you cannot <span>node.next.take()</span><span>).</span>
-
Can you modify next with ref mut node?
Yes, you can do this:
while let Some(ref mut node) = current { // Increase value node.val += 1; // Skip the next node if let Some(skip_node) = node.next.as_mut().and_then(|n| n.next.take()) { node.next = Some(skip_node); } // Move current current = &mut node.next; }
3. Summary
The Option layer is just a “wrapper” and does not automatically dereference. The Box layer is a “smart pointer” that automatically dereferences once. Accessing fields will automatically dereference (<span>box.val</span><span> is valid). Accessing Option requires unwrapping or using as_mut() before continuing.</span>
If you find this useful, please follow me 😊.