I want to write a function that removes the last node from a singly linked list with as little overhead as possible using only safe Rust codes. How good can you do?
Here is the definition of my singly linked list, there is nothing special about it:
struct Node<T> {
value: T,
next: Option<Box<Self>>,
}
type List<T> = Option<Box<Node<T>>>;
For reference, here is a recursive version that works correctly on not very long lists:
fn remove_last_node_recursive<T>(node_ref: &mut List<T>) {
let next_ref = &mut node_ref.as_mut().unwrap().next;
if next_ref.is_some() {
remove_last_node_recursive(next_ref);
} else {
*node_ref = None;
}
}
But this version has two problems:
- Although the recursive call is a tail call (correct me if I’m wrong), tail call optimization isn’t guaranteed on Rust, so for a very long list, stack overflow could happen.
- The recursive call is called with a
Some
variant, but the called function still checks the variant type with aunwrap
call.
For problem 1, I tried to convert it into a iterative version since all recursive calls are tail calls, but failed. This is what I have tried:
fn remove_last_node_iterative<T>(mut node_ref: &mut List<T>) {
loop {
let next_ref = &mut node_ref.as_mut().unwrap().next;
if next_ref.is_some() {
node_ref = next_ref;
} else {
break;
}
}
*node_ref = None; // This line causes lifetime error.
}
The code above does not work due to lifetime error. I’m not sure why. Is it expected that some tail recursive functions can’t be converted into a iterative one? Or is it a bug in Rust? I know that Drop
could cause some fake tail calls but I don’t think this is the case.
I have asked this problem on Stack Overflow, but didn’t get a satisfying answer.
The following implementation is from Stack Overflow:
fn remove_last_node_iterative<T>(mut node_ref: &mut Option<Box<Node<T>>>) {
while node_ref.as_ref().unwrap().next.is_some() {
node_ref = &mut node_ref.as_mut().unwrap().next;
}
*node_ref = None;
}
I think this one has too many unwrap
calls.
For problem 2, I have tried to write a infinitely nested code to get some insight. Of course you can’t really write a infinitely nested code, so my code can only work for lists with up to 5 nodes. Here is my code:
fn remove_last_node_unrolled<T>(first_node_ref: &mut List<T>) {
if let Some(first_node) = first_node_ref {
let second_node_ref = &mut first_node.next;
if let Some(second_node) = second_node_ref {
let third_node_ref = &mut second_node.next;
if let Some(third_node) = third_node_ref {
let fourth_node_ref = &mut third_node.next;
if let Some(fourth_node) = fourth_node_ref {
let fifth_node_ref = &mut fourth_node.next;
if let Some(fifth_node) = fifth_node_ref {
let sixth_node_ref = &mut fifth_node.next;
if let Some(sixth_node) = sixth_node_ref {
unimplemented!();
} else {
*fifth_node_ref = None;
}
} else {
*fourth_node_ref = None;
}
} else {
*third_node_ref = None;
}
} else {
*second_node_ref = None;
}
} else {
*first_node_ref = None;
}
} else {
unreachable!();
}
}
You get the idea. I also can’t convert this one into a loop because I discovered that I need to access a mutable reference to a Option<Box<Node<T>>>
and a mutable reference to Node<T>
that Option<Box<Node<T>>>
owns at the same time so it doesn’t work.
So how would you write the function?