Today we will take a very simple intrusive linked list written in Rust and make it safe. Kind of, anyway.

Before we start making something safe we need an unsafe thing to make safe. Let’s not pretend that what we are doing here is the least bit useful, let us instead do it just for the fun of it. (What we are doing actually is useful, the explanation of which this margin is too narrow to contain.)

Any programmer will have come across linked lists at some point. Linked lists are great because elements can be inserted at any position very quickly, and similarly we can remove elements at any position with very little cost. Intrusive lists are linked lists as well, but unlike the normal linked lists the Rust stdlib provides an intrusive list stores its links in the elements it contains rather than outside of them. (Often this kind of thing is done to avoid memory allocations, or for exposition. Linked lists depicted in computer science textbooks for example are always intrusive.)

The Gathering

If we want to make a safe intrusive linked list from scratch, we must first invent the universe an unsafe linked list. To do so we need a few requirements to guide our implementation. Let’s collect a few at random.

An Element stores a u32 value. An Element may be added only to the front of the list. An Element may be removed regardless of its position in the list, discarding the stored value. An Element may be removed from the beginning of the list, returning the stored value.

To make this happen we need two structures: one, the Element , for the elements of the list, and another, the List , for the list itself. We also need three operations: push to add an element, pop to remove an element and return its value, and unlink to remove an element.

To make push possible our List will need a link to the first element of the list, and our Element will need a link to its successor. Unfortunately we cannot use plain references because removing an element from the list would then be impossible–as long as the element is in the list it is borrowed and cannot be mutated. We also cannot use Cells with references inside because the List would borrow any Element for longer than the element itself lives:

struct Element < 'a > { value : u32 , next : Option <& 'a Element < 'a >> } struct List < 'a > { head : Option <& 'a Element < 'a >> , } fn main () { let mut list = List ( None ); let elem = Element ( None ); list . 0 = Some ( & element ); // boom }

We really want to be able to add elements to a list though rather than replacing the list with an entirely new version (as we would do in functional languages). References are out, then. The only thing we have left now are pointers, which are unsafe. So at least we have the unsafety nailed down already, even though we don’t have any operations yet. The pointers will have to be mutable because adding and removing elements mutates their successors in the list.

1 2 3 4 5 6 7 8 struct Element { value : u32 , next : * mut Element , } struct List { head : * mut Element , }

We can now implement the push and pop operations, but we can’t implement unlink yet. To implement unlink with this setup we need to know which collection the Element is part of, search for the element, and then remove it from its position using the next and head links. This is rather inefficient, so we would like to do better. We can do better if every element knows not only the next element in the list, but also knows which link leads to itself from the previous element or the List instance, whichever it might be.

1 2 3 4 5 6 7 8 9 struct Element { value : u32 , next : * mut Element , ptr_to_self : * mut * mut Element , } struct List { head : * mut Element , }

With this setup we can now implement all three operations. All three are rather simple, moving only a bit of data around between pointers. We have no idea how long any element of the list will live, or even the list itself will live, thus all our operations must be unsafe . Nevertheless we can apply some codpiece engineering (“If you can’t make it safe, at least make it so that nobody gets hurt”) and not attempt to unlink elements that are not in the list (or not in the list anymore).