| # IntoIter |
| |
| Let's move on to writing iterators. `iter` and `iter_mut` have already been |
| written for us thanks to The Magic of Deref. However there's two interesting |
| iterators that Vec provides that slices can't: `into_iter` and `drain`. |
| |
| IntoIter consumes the Vec by-value, and can consequently yield its elements |
| by-value. In order to enable this, IntoIter needs to take control of Vec's |
| allocation. |
| |
| IntoIter needs to be DoubleEnded as well, to enable reading from both ends. |
| Reading from the back could just be implemented as calling `pop`, but reading |
| from the front is harder. We could call `remove(0)` but that would be insanely |
| expensive. Instead we're going to just use ptr::read to copy values out of |
| either end of the Vec without mutating the buffer at all. |
| |
| To do this we're going to use a very common C idiom for array iteration. We'll |
| make two pointers; one that points to the start of the array, and one that |
| points to one-element past the end. When we want an element from one end, we'll |
| read out the value pointed to at that end and move the pointer over by one. When |
| the two pointers are equal, we know we're done. |
| |
| Note that the order of read and offset are reversed for `next` and `next_back` |
| For `next_back` the pointer is always after the element it wants to read next, |
| while for `next` the pointer is always at the element it wants to read next. |
| To see why this is, consider the case where every element but one has been |
| yielded. |
| |
| The array looks like this: |
| |
| ```text |
| S E |
| [X, X, X, O, X, X, X] |
| ``` |
| |
| If E pointed directly at the element it wanted to yield next, it would be |
| indistinguishable from the case where there are no more elements to yield. |
| |
| Although we don't actually care about it during iteration, we also need to hold |
| onto the Vec's allocation information in order to free it once IntoIter is |
| dropped. |
| |
| So we're going to use the following struct: |
| |
| <!-- ignore: simplified code --> |
| ```rust,ignore |
| pub struct IntoIter<T> { |
| buf: NonNull<T>, |
| cap: usize, |
| start: *const T, |
| end: *const T, |
| } |
| ``` |
| |
| And this is what we end up with for initialization: |
| |
| <!-- ignore: simplified code --> |
| ```rust,ignore |
| impl<T> IntoIterator for Vec<T> { |
| type Item = T; |
| type IntoIter = IntoIter<T>; |
| fn into_iter(self) -> IntoIter<T> { |
| // Make sure not to drop Vec since that would free the buffer |
| let vec = ManuallyDrop::new(self); |
| |
| // Can't destructure Vec since it's Drop |
| let ptr = vec.ptr; |
| let cap = vec.cap; |
| let len = vec.len; |
| |
| IntoIter { |
| buf: ptr, |
| cap, |
| start: ptr.as_ptr(), |
| end: if cap == 0 { |
| // can't offset off this pointer, it's not allocated! |
| ptr.as_ptr() |
| } else { |
| unsafe { ptr.as_ptr().add(len) } |
| }, |
| } |
| } |
| } |
| ``` |
| |
| Here's iterating forward: |
| |
| <!-- ignore: simplified code --> |
| ```rust,ignore |
| impl<T> Iterator for IntoIter<T> { |
| type Item = T; |
| fn next(&mut self) -> Option<T> { |
| if self.start == self.end { |
| None |
| } else { |
| unsafe { |
| let result = ptr::read(self.start); |
| self.start = self.start.offset(1); |
| Some(result) |
| } |
| } |
| } |
| |
| fn size_hint(&self) -> (usize, Option<usize>) { |
| let len = (self.end as usize - self.start as usize) |
| / mem::size_of::<T>(); |
| (len, Some(len)) |
| } |
| } |
| ``` |
| |
| And here's iterating backwards. |
| |
| <!-- ignore: simplified code --> |
| ```rust,ignore |
| impl<T> DoubleEndedIterator for IntoIter<T> { |
| fn next_back(&mut self) -> Option<T> { |
| if self.start == self.end { |
| None |
| } else { |
| unsafe { |
| self.end = self.end.offset(-1); |
| Some(ptr::read(self.end)) |
| } |
| } |
| } |
| } |
| ``` |
| |
| Because IntoIter takes ownership of its allocation, it needs to implement Drop |
| to free it. However it also wants to implement Drop to drop any elements it |
| contains that weren't yielded. |
| |
| <!-- ignore: simplified code --> |
| ```rust,ignore |
| impl<T> Drop for IntoIter<T> { |
| fn drop(&mut self) { |
| if self.cap != 0 { |
| // drop any remaining elements |
| for _ in &mut *self {} |
| let layout = Layout::array::<T>(self.cap).unwrap(); |
| unsafe { |
| alloc::dealloc(self.buf.as_ptr() as *mut u8, layout); |
| } |
| } |
| } |
| } |
| ``` |