|
| 1 | +- Feature Name: `linked_list_cursors` |
| 2 | +- Start Date: 2018-10-14 |
| 3 | +- RFC PR: [rust-lang/rfcs#2570](https://github.com/rust-lang/rfcs/pull/2570) |
| 4 | +- Rust Issue: [rust-lang/rust#58533](https://github.com/rust-lang/rust/issues/58533) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Many of the benefits of linked lists rely on the fact that most operations |
| 10 | +(insert, remove, split, splice etc.) can be performed in constant time once one |
| 11 | +reaches the desired element. To take advantage of this, a `Cursor` interface |
| 12 | +can be created to efficiently edit linked lists. Furthermore, unstable |
| 13 | +extensions like the `IterMut` changes will be removed. |
| 14 | + |
| 15 | +# Motivation |
| 16 | +[motivation]: #motivation |
| 17 | + |
| 18 | +From Programming Rust: |
| 19 | +> As of Rust 1.12, Rust’s LinkedList type has no methods for removing a range of |
| 20 | +> elements from a list or inserting elements at specific locations in a list. |
| 21 | +> The API seems incomplete. |
| 22 | +
|
| 23 | +Both of these issues have been fixed, but in different and incompatible ways. |
| 24 | +Removing a range of elements is possible though the unstable `drain_filter` API, |
| 25 | +and inserting elements in at specific locations in a list is possible through |
| 26 | +the `linked_list_extras` extensions to `IterMut`. |
| 27 | + |
| 28 | +This motivates the need for a standard interface for insertion and deletion of |
| 29 | +elements in a linked list. An efficient way to implement this is through the use |
| 30 | +of "cursors". A cursor represents a position in a collection that can be moved |
| 31 | +back and forth, somewhat like a `DoubleEndedIterator`. However, mutable cursors |
| 32 | +can also edit the collection at their position. |
| 33 | + |
| 34 | +A mutable cursor would allow for constant time insertion and deletion of |
| 35 | +elements and insertion and splitting of lists at its position. This would allow |
| 36 | +for simplification of the `IterMut` API and a complete LinkedList |
| 37 | +implementation. |
| 38 | + |
| 39 | +# Guide-level explanation |
| 40 | +[guide-level-explanation]: #guide-level-explanation |
| 41 | + |
| 42 | +The cursor interface would provides two new types: `Cursor` and `CursorMut`. |
| 43 | +These are created in the same way as iterators. |
| 44 | + |
| 45 | +With a `Cursor` one can seek back and forth through a list and get the current |
| 46 | +element. With a `CursorMut` One can seek back and forth and get mutable |
| 47 | +references to elements, and it can insert and delete elements before and behind |
| 48 | +the current element (along with performing several list operations such as |
| 49 | +splitting and splicing). |
| 50 | + |
| 51 | +Lets look at where these might be useful. |
| 52 | + |
| 53 | +## Examples |
| 54 | + |
| 55 | +This interface is helpful most times insertion and deletion are used together. |
| 56 | + |
| 57 | +For example, consider you had a linked list and wanted to remove all elements |
| 58 | +which satisfy a certain predicate, and replace them with another element. With |
| 59 | +the old interface, one would have to insert and delete separately, or split the |
| 60 | +list many times. With the cursor interface, one can do the following: |
| 61 | + |
| 62 | +``` rust |
| 63 | +fn remove_replace<T, P, F>(list: &mut LinkedList<T>, p: P, f: F) |
| 64 | + where P: Fn(&T) -> bool, F: Fn(T) -> T |
| 65 | +{ |
| 66 | + let mut cursor = list.cursor_mut(); |
| 67 | + // move to the first element, if it exists |
| 68 | + loop { |
| 69 | + let should_replace = match cursor.peek() { |
| 70 | + Some(element) => p(element), |
| 71 | + None => break, |
| 72 | + }; |
| 73 | + if should_replace { |
| 74 | + let old_element = cursor.pop().unwrap(); |
| 75 | + cursor.insert(f(old_element)); |
| 76 | + } |
| 77 | + cursor.move_next(); |
| 78 | + } |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +This could also be done using iterators. One could transform the list into an |
| 83 | +iterator, perform operations on it and collect. This is easier, however it still |
| 84 | +requires much needless allocation. |
| 85 | + |
| 86 | +For another example, consider code that was previously using `IterMut` |
| 87 | +extensions. |
| 88 | +``` rust |
| 89 | +fn main() { |
| 90 | + let mut list: LinkedList<_> = (0..10).collect(); |
| 91 | + let mut iter = list.iter_mut(); |
| 92 | + while let Some(x) = iter.next() { |
| 93 | + if x >= 5 { |
| 94 | + break; |
| 95 | + } |
| 96 | + } |
| 97 | + iter.insert_next(12); |
| 98 | +} |
| 99 | +``` |
| 100 | +This can be changed almost verbatim to `CursorMut`: |
| 101 | +``` rust |
| 102 | +fn main() { |
| 103 | + let mut list: LinkedList<_> = (0..10).collect(); |
| 104 | + let mut cursor = list.cursor_mut() { |
| 105 | + while let Some(x) = cursor.peek_next() { |
| 106 | + if x >= 5 { |
| 107 | + break; |
| 108 | + } |
| 109 | + cursor.move_next(); |
| 110 | + } |
| 111 | + cursor.insert(12); |
| 112 | +} |
| 113 | +``` |
| 114 | +In general, the cursor interface is not the easiest way to do something. |
| 115 | +However, it provides a basic API that can be built on to perform more |
| 116 | +complicated tasks. |
| 117 | + |
| 118 | +# Reference-level explanation |
| 119 | +[reference-level-explanation]: #reference-level-explanation |
| 120 | + |
| 121 | +One gets a cursor the exact same way as one would get an iterator. The |
| 122 | +returned cursor would point to the "empty" element, i.e. if you got an element |
| 123 | +and called `current` you would receive `None`. |
| 124 | +``` rust |
| 125 | +// Provides a cursor to the first element of the list |
| 126 | +pub fn cursor(&self) -> Cursor<T>; |
| 127 | + |
| 128 | +/// Provides a cursor with mutable references and access to the list |
| 129 | +pub fn cursor_mut(&mut self) -> CursorMut<T>; |
| 130 | +``` |
| 131 | + |
| 132 | +These would provide the following interface: |
| 133 | + |
| 134 | +``` rust |
| 135 | +impl<'list, T> Cursor<'list, T> { |
| 136 | + /// Move to the subsequent element of the list if it exists or the empty |
| 137 | + /// element |
| 138 | + pub fn move_next(&mut self); |
| 139 | + /// Move to the previous element of the list |
| 140 | + pub fn move_prev(&mut self); |
| 141 | + |
| 142 | + /// Get the current element |
| 143 | + pub fn current(&self) -> Option<&'list T>; |
| 144 | + /// Get the following element |
| 145 | + pub fn peek(&self) -> Option<&'list T>; |
| 146 | + /// Get the previous element |
| 147 | + pub fn peek_before(&self) -> Option<&'list T>; |
| 148 | +} |
| 149 | + |
| 150 | +impl<'list T> CursorMut<'list, T> { |
| 151 | + /// Move to the subsequent element of the list if it exists or the empty |
| 152 | + /// element |
| 153 | + pub fn move_next(&mut self); |
| 154 | + /// Move to the previous element of the list |
| 155 | + pub fn move_prev(&mut self); |
| 156 | + |
| 157 | + /// Get the current element |
| 158 | + pub fn current(&mut self) -> Option<&mut T>; |
| 159 | + /// Get the next element |
| 160 | + pub fn peek(&mut self) -> Option<&mut T>; |
| 161 | + /// Get the previous element |
| 162 | + pub fn peek_before(&mut self) -> Option<&mut T>; |
| 163 | + |
| 164 | + /// Get an immutable cursor at the current element |
| 165 | + pub fn as_cursor<'cm>(&'cm self) -> Cursor<'cm, T>; |
| 166 | + |
| 167 | + // Now the list editing operations |
| 168 | + |
| 169 | + /// Insert `item` after the cursor |
| 170 | + pub fn insert(&mut self, item: T); |
| 171 | + /// Insert `item` before the cursor |
| 172 | + pub fn insert_before(&mut self, item: T); |
| 173 | + |
| 174 | + /// Remove and return the item following the cursor |
| 175 | + pub fn pop(&mut self) -> Option<T>; |
| 176 | + /// Remove and return the item before the cursor |
| 177 | + pub fn pop_before(&mut self) -> Option<T>; |
| 178 | + |
| 179 | + /// Insert `list` between the current element and the next |
| 180 | + pub fn insert_list(&mut self, list: LinkedList<T>); |
| 181 | + /// Insert `list` between the previous element and current |
| 182 | + pub fn insert_list_before(&mut self, list: LinkedList<T>); |
| 183 | + |
| 184 | + /// Split the list in two after the current element |
| 185 | + /// The returned list consists of all elements following the current one. |
| 186 | + // note: consuming the cursor is not necessary here, but it makes sense |
| 187 | + // given the interface |
| 188 | + pub fn split(self) -> LinkedList<T>; |
| 189 | + /// Split the list in two before the current element |
| 190 | + pub fn split_before(self) -> LinkedList<T>; |
| 191 | +} |
| 192 | +``` |
| 193 | +One should closely consider the lifetimes in this interface. Both `Cursor` and |
| 194 | +`CursorMut` operate on data in their `LinkedList`. This is why, they both hold |
| 195 | +the annotation of `'list`. |
| 196 | + |
| 197 | +The lifetime elision for their constructors is correct as |
| 198 | +``` |
| 199 | +pub fn cursor(&self) -> Cursor<T> |
| 200 | +``` |
| 201 | +becomes |
| 202 | +``` |
| 203 | +pub fn cursor<'list>(&'list self) -> Cursor<'list, T> |
| 204 | +``` |
| 205 | +which is what we would expect. (the same goes for `CursorMut`). |
| 206 | + |
| 207 | +Since `Cursor` cannot mutate its list, `current`, `peek` and `peek_before` all |
| 208 | +live as long as `'list`. However, in `CursorMut` we must be careful to make |
| 209 | +these methods borrow. Otherwise, one could produce multiple mutable references |
| 210 | +to the same element. |
| 211 | + |
| 212 | +The only other lifetime annotation is with `as_cursor`. In this case, the |
| 213 | +returned `Cursor` must borrow its generating `CursorMut`. Otherwise, it would be |
| 214 | +possible to achieve a mutable and immutable reference to the same element at |
| 215 | +once. |
| 216 | + |
| 217 | +One question that arises from this interface is what happens if `move_next` is |
| 218 | +called when a cursor is on the last element of the list, or is empty (or |
| 219 | +`move_prev` and the beginning). A simple way to solve this is to make cursors |
| 220 | +wrap around this list back to the empty element. One could complicate the |
| 221 | +interface by having move return a `bool`, however this is unnecessary since |
| 222 | +`current` is sufficient to know whether the iterator is at the end of the list. |
| 223 | + |
| 224 | +A large consequence of this new interface is that it is a complete superset of |
| 225 | +the already existing `Iter` and `IterMut` API. Therefore, the following two |
| 226 | +methods added to `IterMut` in the `linked_list_extras` features should be |
| 227 | +removed or depreciated: |
| 228 | +- `IterMut::insert_next` |
| 229 | +- `IterMut::peek_next` |
| 230 | +The rest of the iterator methods are stable and should probably stay untouched |
| 231 | +(but see below for comments). |
| 232 | + |
| 233 | +# Drawbacks |
| 234 | +[drawbacks]: #drawbacks |
| 235 | + |
| 236 | +The cursor interface is rather clunky, and while it allows for efficient code, |
| 237 | +it is probably not useful outside of many use-cases. |
| 238 | + |
| 239 | +One of the largest issues with the cursor interface is that it exposes the exact |
| 240 | +same interface of iterators (and more), which leads to unnecessary code |
| 241 | +duplication. |
| 242 | +However, the purpose of iterators seems to be simple, abstract and easy to use |
| 243 | +rather than efficient mutation, so cursors and iterators should be used |
| 244 | +in different places. |
| 245 | + |
| 246 | +# Rationale and alternatives |
| 247 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 248 | + |
| 249 | +There are several alternatives to this: |
| 250 | + |
| 251 | +1. Implement cursors as a trait extending `Iterator` (see the cursors |
| 252 | +pseudo-rfc below) |
| 253 | + |
| 254 | +Since the cursors are just an extension of iterators, it makes some sense to |
| 255 | +create them as a trait. However, I see several reasons why this is not the best. |
| 256 | + |
| 257 | +First, cursors work differently than the existing `Iterator` extensions like |
| 258 | +`DoubleEndedIterator`. In a `DoubleEndedIterator`, if one calls `next_back` and |
| 259 | +then `next`, it should not return the same value, so unlike a cursor, a |
| 260 | +`DoubleEndedIterator` does not move back and forth throughout a collection. |
| 261 | + |
| 262 | +Furthermore, while `Iterator` is a general interface for many collections, |
| 263 | +`Cursor` is very much specific to linked lists. In other collections such as |
| 264 | +`Vec` a cursor does not make sense. So it makes little sense to make a trait |
| 265 | +when it will only be used in one place. |
| 266 | + |
| 267 | +2. Using the `IterMut` linked list extensions |
| 268 | + |
| 269 | +Insertion was added to `IterMut` in the `linked_list_extras` feature. Many of |
| 270 | +these features could be added to it just as well. But, this overcrowds `IterMut` |
| 271 | +with many methods that have nothing to do with iteration (such as deletion, |
| 272 | +splitting etc.) |
| 273 | +It makes sense to put these explicitly in their own type, and this can be |
| 274 | +`CursorMut`. |
| 275 | + |
| 276 | +3. Do not create cursors at all |
| 277 | + |
| 278 | +Everything that cursors do can already be done, albeit in sometimes a less |
| 279 | +efficient way. Efficient code can be written by splitting linked lists often, |
| 280 | +and while this is a complicated way to do things, the rarity of the use case may |
| 281 | +justify keeping things how they are. |
| 282 | + |
| 283 | +# Prior art |
| 284 | +[prior-art]: #prior-art |
| 285 | + |
| 286 | +- [cursors pseudo-rfc](https://internals.rust-lang.org/t/pseudo-rfc-cursors-reversible-iterators/386/18) |
| 287 | + |
| 288 | +This rust internals post describes an early attempt at making cursors. The |
| 289 | +language was in a different state when it was written (pre-1.0), so details have |
| 290 | +changed since then. But this describes several different approaches to making |
| 291 | +cursors and where they led. |
| 292 | + |
| 293 | +- Java-style iterators |
| 294 | + |
| 295 | +Java (and other languages) tried to fix this by adding a `remove` function to |
| 296 | +their iterators. However, I feel this method would not be the best choice for |
| 297 | +Rust (even for specific `IterMut`s like those in LinkedList) since it diverges |
| 298 | +from the expected behaviour of iterators. |
| 299 | + |
| 300 | +- [linked list extras issue](https://github.com/rust-lang/rust/issues/27794) |
| 301 | + |
| 302 | +Discussion on the issue tracker about how this is currently managed with |
| 303 | +modifications to `IterMut`. The consensus seems to be that it is incomplete, and |
| 304 | +it is suggested to create a new `Cursor` and `CursorMut` types. |
| 305 | + |
| 306 | +# Unresolved questions |
| 307 | +[unresolved-questions]: #unresolved-questions |
| 308 | + |
| 309 | +- How will this interface interact with iterators? |
| 310 | + |
| 311 | +Will we keep both `Iter` and `Cursor` types? Implement one with another? I feel |
| 312 | +like they should be different things, but there is reason to consolidate them. |
| 313 | + |
| 314 | +- Only for linked lists? |
| 315 | + |
| 316 | +Should we implement this for more collections? It could make sense for other |
| 317 | +collections, such as trees and arrays, but the design would have to be reworked. |
0 commit comments