program.
We said we don't want to use intrinsics, so doing *exactly* what `std` does is
-out. `std::rt::util::abort` actually exists, but it takes a message to print,
-which will probably allocate. Also it's still unstable. Instead, we'll call
-`std::process::exit` with some random number.
+out. Instead, we'll call `std::process::exit` with some random number.
```rust
fn oom() {
allocate()
cap = 1
else:
- reallocate
+ reallocate()
cap *= 2
```
and they can't just be merged afterwards.
When you use GEP inbounds, you are specifically telling LLVM that the offsets
-you're about to do are within the bounds of a single allocated entity. The
+you're about to do are within the bounds of a single "allocated" entity. The
ultimate payoff being that LLVM can assume that if two pointers are known to
point to two disjoint objects, all the offsets of those pointers are *also*
known to not alias (because you won't just end up in some random place in
and it's fine to pretend that there's infinite zero-sized types allocated
at `0x01`. No allocator will ever allocate that address, because they won't
allocate `0x00` and they generally allocate to some minimal alignment higher
-than a byte.
+than a byte. Also generally the whole first page of memory is
+protected from being allocated anyway (a whole 4k, on many platforms).
However what about for positive-sized types? That one's a bit trickier. In
principle, you can argue that offsetting by 0 gives LLVM no information: either
pub fn into_iter(self) -> IntoIter<T> {
unsafe {
let iter = RawValIter::new(&self);
+
let buf = ptr::read(&self.buf);
mem::forget(self);
impl<'a, T> Iterator for Drain<'a, T> {
type Item = T;
- fn next(&mut self) -> Option<T> { self.iter.next_back() }
+ fn next(&mut self) -> Option<T> { self.iter.next() }
fn size_hint(&self) -> (usize, Option<usize>) { self.iter.size_hint() }
}
% Layout
-First off, we need to come up with the struct layout. Naively we want this
-design:
+First off, we need to come up with the struct layout. A Vec has three parts:
+a pointer to the allocation, the size of the allocation, and the number of
+elements that have been initialized.
+
+Naively, this means we just want this design:
```rust
pub struct Vec<T> {
% RawVec
We've actually reached an interesting situation here: we've duplicated the logic
-for specifying a buffer and freeing its memory. Now that we've implemented it
-and identified *actual* logic duplication, this is a good time to perform some
-logic compression.
+for specifying a buffer and freeing its memory in Vec and IntoIter. Now that
+we've implemented it and identified *actual* logic duplication, this is a good
+time to perform some logic compression.
We're going to abstract out the `(ptr, cap)` pair and give them the logic for
allocating, growing, and freeing:
}
```
-And change vec as follows:
+And change Vec as follows:
```rust,ignore
pub struct Vec<T> {
many intrinsics *do* become stabilized elsewhere (`std::ptr` and `str::mem`
consist of many intrinsics).
-Ultimately this means out implementation may not take advantage of all
+Ultimately this means our implementation may not take advantage of all
possible optimizations, though it will be by no means *naive*. We will
definitely get into the weeds over nitty-gritty details, even
when the problem doesn't *really* merit it.