| # 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); | |
| } | |
| } | |
| } | |
| } | |
| ``` |