Skip to content

Commit 906cbe2

Browse files
committed
wip sixth
1 parent 20f289b commit 906cbe2

File tree

4 files changed

+143
-5
lines changed

4 files changed

+143
-5
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
* [Extras](fifth-extras.md)
4444
* [Final Code](fifth-final.md)
4545
* [A Production Unsafe Deque](sixth.md)
46+
* [Layout](sixth-layout.md)
47+
* [Unsafe](sixth-basics.md)
4648
* [A Bunch of Silly Lists](infinity.md)
4749
* [The Double Single](infinity-double-single.md)
4850
* [The Stack-Allocated Linked List](infinity-stack-allocated.md)

src/sixth-basics.md

Whitespace-only changes.

src/sixth-layout.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Layout
2+
3+
Let us begin by first studying the structure of our enemy. A Doubly-Linked List is conceptually simple, but that's how it decieves and manipulates you. It's the same kind of linked list we've looked at over and over, but the links go both ways. Double the links, double the evil.
4+
5+
So rather than this (gonna drop the Some/None stuff to keep it cleaner):
6+
7+
```text
8+
... -> (A, ptr) -> (B, ptr) -> ...
9+
```
10+
11+
We have this:
12+
13+
```text
14+
... <-> (ptr, A, ptr) <-> (ptr, B, ptr) <-> ...
15+
```
16+
17+
This lets you traverse the list from either direction, or seek back and forth with a [cursor](https://doc.rust-lang.org/std/collections/struct.LinkedList.html#method.cursor_back_mut).
18+
19+
In exchange for this flexibility, every node has to store twice as many pointers, and every operations has to fix up way more pointers. It's a significant enough complication that it's a lot easier to make a mistake, so we're going to be doing a lot of testing.
20+
21+
You might have also noticed that I intentionally haven't drawn the *ends* of the list. This is because this one of the places where there are genuinely defensible options for our implementation. We *definitely* need our implementation to have two pointers: one to the start of the list, and one to the end of the list.
22+
23+
There are two notable ways to do this in my mind: "traditional" and "dummy node".
24+
25+
The traditional approach is the simple extension of how we did a Stack &mdash; just store the head and tail pointers on the stack:
26+
27+
```text
28+
[ptr, ptr] <-> (ptr, A, ptr) <-> (ptr, B, ptr)
29+
^ ^
30+
+----------------------------------------+
31+
```
32+
33+
This is fine, but it has one downside: corner cases. There are now two edges to our list, which means twice as many corner cases. It's easy to forget one and have a serious bug.
34+
35+
The dummy node approach attempts to smooth out these corner cases by adding an extra node to our list which contains no data but links the two ends together into a ring:
36+
37+
```text
38+
[ptr] -> (ptr, ?DUMMY?, ptr) <-> (ptr, A, ptr) <-> (ptr, B, ptr)
39+
^ ^
40+
+-------------------------------------------------+
41+
```
42+
43+
By doing this, every node *always* has actual pointers to a previous and next node in the list. Even when you remove the last element from the list, you just end up stitching the dummy node to point at itself:
44+
45+
```text
46+
[ptr] -> (ptr, ?DUMMY?, ptr)
47+
^ ^
48+
+-------------+
49+
```
50+
51+
There is a part of me that finds this *very* satisfying and elegant. Unfortunately, it has a couple practical problems:
52+
53+
Problem 1: An extra indirection and allocation, especially for the empty list, which must include the dummy node. Potential solutions include:
54+
55+
* Don't allocate the dummy node until something is inserted: simple and effective, but it adds back some of the corner cases we were trying to avoid by using dummy pointers!
56+
57+
* Use a static copy-on-write empty singleton dummy node, with some really clever scheme that lets the Copy-On-Write checks piggy-back on normal checks: look I'm really tempted, I really do love that shit, but we can't go down that dark path in this book. Read [ThinVec's sourcecode](https://docs.rs/thin-vec/0.2.4/src/thin_vec/lib.rs.html#319-325) if you want to see that kind of perverted stuff.
58+
59+
* Store the dummy node on the stack - not practical in a language without C++-style move-constructors. I'm sure there's something weird thing we could do here with [pinning](https://doc.rust-lang.org/std/pin/index.html) but we're not gonna.
60+
61+
Problem 2: What *value* is stored in the dummy node? Sure if it's an integer it's fine, but what if we're storing a list full of `Box`? It may be impossible for us to initialized this value! Potential solutions include:
62+
63+
* Make every node store `Option<T>`: simple and effective, but also bloated and annoying.
64+
65+
* Make every node store [`MaybeUninit<T>`](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html). Horrifying and annoying.
66+
67+
* *Really* careful and clever inheritance-style type punning so the dummy node doesn't include the data field. This is also tempting but it's extremely dangerous and annoying. Read [BTreeMap's sourcecode](https://doc.rust-lang.org/1.55.0/src/alloc/collections/btree/node.rs.html#49-104) if you want to see that kind of perverted stuff.
68+
69+
The problems really outweight the convenience for a language like Rust, so we're going to stick to the traditional layout. We'll be using the same basic design as we did for the unsafe queue in the previous chapter:
70+
71+
```rust
72+
pub type Link<T> = *mut Node<T>;
73+
74+
pub struct List<T> {
75+
front: Link<T>,
76+
back: Link<T>,
77+
}
78+
79+
pub struct Node<T> {
80+
prev: Link<T>,
81+
next: Link<T>,
82+
elem: T,
83+
}
84+
```
85+
86+
This isn't quite a *true* production-quality layout yet. It's *fine* but there's magic tricks we can do to tell Rust what we're doing a bit better. Something we'll look at later. Maybe.

src/sixth.md

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,61 @@
1-
# An Ok Unsafe Doubly-Linked Deque
1+
# An Production Unsafe Doubly-Linked Deque
22

3-
Nope, still haven't written this one! It's really just not that much more instructive.
3+
We finally made it. My greatests nemesis: **[std::collections::LinkedList][linked-list], the Doubly-Linked Deque**.
4+
5+
The one that I tried and failed to destroy.
6+
7+
Our story begins as 2014 was coming to a close and we were rapidly approaching the release of Rust 1.0, Rust's first stable release. I had found myself in the role of caring for `std::collections`, or as we affectionately called it in those times, libcollections.
8+
9+
libcollections had spent years as a dumping ground for everyone's Cute Ideas and Vaguely Useful Things. This was all well and good when Rust was a fledgling experimental language, but if my children were going to escape the nest and be stabilized, they would have to prove their worth.
10+
11+
Until then I had encouraged and nurtured them all, but it was now time for them to face judgement for their failings.
12+
13+
I sunk my claws into the bedrock and carved tombstones for my most foolish children. A grisly monument that I placed in the town square for all to see:
14+
15+
**[Kill TreeMap, TreeSet, TrieMap, TrieSet, LruCache and EnumSet](https://github.com/rust-lang/rust/pull/19955)**
16+
17+
Their fates were sealed, for my word was absolute. The other collections were horrified by my brutality, but they were not yet safe from their mother's wrath. I soon returned with two more tombstones:
18+
19+
**[Deprecate BitSet and BitVec](https://github.com/rust-lang/rust/pull/26034)**
20+
21+
The Bit twins were more cunning than their fallen comrades, but they lacked the strength to escape me. Most thought my work done, but I soon took one more:
22+
23+
**[Deprecate VecMap](https://github.com/rust-lang/rust/pull/26734)**
24+
25+
VecMap had tried to survive through stealth &mdash; it was so small and inoffensive! But that wasn't enough for the libcollections I saw in my vision of the future.
26+
27+
I surveyed the land and saw what remained:
28+
29+
* Vec and VecDeque - hearty and simple, the heart of computing.
30+
* HashMap and HashSet - powerful and wise, the brain of computing.
31+
* BTreeMap and BTreeSet - awkward but necessary, the liver of computing.
32+
* BinaryHeap - crafty and dextrous, the ankle of computing.
33+
34+
I nodded in contentment. Simple and effective. My work was don&mdash;
35+
36+
No, [DList](https://github.com/rust-lang/rust/blob/0a84308ebaaafb8fd89b2fd7c235198e3ec21384/src/libcollections/dlist.rs), it can't be! I thought you died in that tragic garbage collection incident! The one which was definitely an accident and not intentional at all!
37+
38+
They had faked their death and taken on a new name, but it was still them: LinkedList, the shadowy and untrustworthy schemer of computing.
39+
40+
I spread word of their misdeeds to all that would hear me, but hearts were unmoved. LinkedList was a silver-tongued devil who had convinced everyone around me that it was some sort of fundamental and natural datastructure of computing. It had even convinced C++ that it was [*the* list](https://en.cppreference.com/w/cpp/container/list)!
41+
42+
"How could you have a standard library without a *LinkedList*?"
43+
44+
Easily! Trivially!
45+
46+
"It's non-trivial unsafe code, so it makes sense to have it in the standard library!"
47+
48+
So are GPU drivers and video codecs, libcollections is minimalist!
49+
50+
But alas, LinkedList had gathered too many allies and grown too strong while I was distracted with its kin.
51+
52+
I fled to my laboratory and tried to devise some sort of [evil clone](https://github.com/contain-rs/linked-list) or [enhanced cyborg replicant](https://github.com/contain-rs/blist) that could rival and destroy it, but my grant funding ran out because my research was "too murderously evil" or somesuch nonsense.
53+
54+
LinkedList had won. I was defeated and forced into exile.
55+
56+
But you're here now. You've come this far. Surely now you can understand the depths of LinkedList's debauchery! Come, I will you show you everything you need to know to help me destroy it once and for all &mdash; everything you need to know to implement an unsafe production-quality Doubly-Linked Deque.
457

5-
Read [The Rustonomicon][] and the source for [std::collections::LinkedList][linked-list] if you
6-
really want more!
758

859

960

10-
[The Rustonomicon]: https://doc.rust-lang.org/nightly/nomicon/
1161
[linked-list]: https://github.com/rust-lang/rust/blob/master/library/alloc/src/collections/linked_list.rs

0 commit comments

Comments
 (0)