Implementing a LinkedList in Rust
TL;DR
Recently, I've been thinking about working through Data Structures and Algorithms in Rust, and I was implementing a doubly-linked list (DLList), which turned out to be quite difficult, so here are my notes. I basically implemented it while referencing the official LinkedList.
Requirements
| Method Name | Behavior |
|---|---|
get(i) | View the i-th element |
set(i, x) | Set the i-th element to x |
add(i, x) | Add x at the i-th position |
remove(i) | Remove the i-th element |
We'll implement these four methods.
Implementation
Struct Definition
The official implementation uses NonNull, so I followed suit. It requires heavy use of unsafe, which is scary, but since the official code does it this way, I decided to use unsafe. I've seen some mentions that using RefCell, Rc, and Weak is also a good approach. I'd like to try an implementation without unsafe sometime. Also, I need to study PhantomData because I don't fully understand it yet.
Defining get and set
The first thing we want to implement is a function that retrieves the NonNull<Node<T>> at the i-th position. Looking at the official code closely, it defines a Cursor-like struct. It holds the current index and the pointer at that point, and can move via next and prev. The official code also defines a CursorMut, which feels more Rust-like, but it wasn't necessary for our requirements, so I omitted it.
Using the Cursor, we write the logic to retrieve the pointer to the i-th node. It traverses from whichever end (head or tail) is closer. get and set simply extract or modify the value from this pointer, so they're straightforward.
Defining add
To perform add, we need to insert a new node before the node at the desired position (e). In other words:
The official implementation of this logic is in a method called splice_nodes. The official implementation is a bit more complex -- given the head and tail pointers of another list, it can do:
However, since we only need to insert one node, I wrote a simplified version. I implemented splice_node which takes e.prev, e, and the node to be added as arguments. For add, we just get the pointer at the desired position with get_node and call splice_node.
Implementing remove
If we call the node we want to remove n_i, we can see that we need to do the following:
This logic is implemented in a function called unlink_node. The processing is exactly as described above. remove, just like add, gets the pointer to the i-th node and passes it to unlink_node.
Thoughts
Reading the official code is really educational.