Skip to content

Commit 83e4153

Browse files
committed
Adjust Vec to build on stable Rust
1 parent 8551afb commit 83e4153

10 files changed

+304
-295
lines changed

src/vec-alloc.md

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,52 @@
11
# Allocating Memory
22

3-
Using Unique throws a wrench in an important feature of Vec (and indeed all of
4-
the std collections): an empty Vec doesn't actually allocate at all. So if we
5-
can't allocate, but also can't put a null pointer in `ptr`, what do we do in
6-
`Vec::new`? Well, we just put some other garbage in there!
3+
Using NonNull throws a wrench in an important feature of Vec (and indeed all of
4+
the std collections): creating an empty Vec doesn't actually allocate at all. This
5+
is not the same as allocating a zero-sized memory block, which is not allowed by
6+
the global allocator (it results in undefined behavior!). So if we can't allocate,
7+
but also can't put a null pointer in `ptr`, what do we do in `Vec::new`? Well, we
8+
just put some other garbage in there!
79

810
This is perfectly fine because we already have `cap == 0` as our sentinel for no
911
allocation. We don't even need to handle it specially in almost any code because
1012
we usually need to check if `cap > len` or `len > 0` anyway. The recommended
11-
Rust value to put here is `mem::align_of::<T>()`. Unique provides a convenience
12-
for this: `Unique::dangling()`. There are quite a few places where we'll
13+
Rust value to put here is `mem::align_of::<T>()`. NonNull provides a convenience
14+
for this: `NonNull::dangling()`. There are quite a few places where we'll
1315
want to use `dangling` because there's no real allocation to talk about but
1416
`null` would make the compiler do bad things.
1517

1618
So:
1719

1820
```rust,ignore
21+
use std::mem;
22+
1923
impl<T> Vec<T> {
2024
fn new() -> Self {
2125
assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
22-
Vec { ptr: Unique::dangling(), len: 0, cap: 0 }
26+
Vec {
27+
ptr: NonNull::dangling(),
28+
len: 0,
29+
cap: 0,
30+
_marker: PhantomData
31+
}
2332
}
2433
}
34+
# fn main() {}
2535
```
2636

2737
I slipped in that assert there because zero-sized types will require some
2838
special handling throughout our code, and I want to defer the issue for now.
2939
Without this assert, some of our early drafts will do some Very Bad Things.
3040

31-
Next we need to figure out what to actually do when we *do* want space. For
32-
that, we'll need to use the rest of the heap APIs. These basically allow us to
33-
talk directly to Rust's allocator (`malloc` on Unix platforms and `HeapAlloc`
34-
on Windows by default).
41+
Next we need to figure out what to actually do when we *do* want space. For that,
42+
we use the global allocation functions [`alloc`][alloc], [`realloc`][realloc],
43+
and [`dealloc`][dealloc] which are available in stable Rust in
44+
[`std::alloc`][std_alloc]. These functions are expected to become deprecated in
45+
favor of the methods of [`std::alloc::Global`][Global] after this type is stabilized.
3546

3647
We'll also need a way to handle out-of-memory (OOM) conditions. The standard
37-
library calls `std::alloc::oom()`, which in turn calls the `oom` langitem,
38-
which aborts the program in a platform-specific manner.
48+
library provides a function [`alloc::handle_alloc_error`][handle_alloc_error],
49+
which will abort the program in a platform-specific manner.
3950
The reason we abort and don't panic is because unwinding can cause allocations
4051
to happen, and that seems like a bad thing to do when your allocator just came
4152
back with "hey I don't have any more memory".
@@ -152,52 +163,48 @@ such we will guard against this case explicitly.
152163
Ok with all the nonsense out of the way, let's actually allocate some memory:
153164

154165
```rust,ignore
155-
fn grow(&mut self) {
156-
// this is all pretty delicate, so let's say it's all unsafe
157-
unsafe {
158-
let elem_size = mem::size_of::<T>();
159-
160-
let (new_cap, ptr) = if self.cap == 0 {
161-
let ptr = Global.allocate(Layout::array::<T>(1).unwrap());
162-
(1, ptr)
166+
use std::alloc::{self, Layout};
167+
168+
impl<T> Vec<T> {
169+
fn grow(&mut self) {
170+
let (new_cap, new_layout) = if self.cap == 0 {
171+
(1, Layout::array::<T>(1).unwrap())
163172
} else {
164-
// as an invariant, we can assume that `self.cap < isize::MAX`,
165-
// so this doesn't need to be checked.
166-
let new_cap = 2 * self.cap;
167-
// Similarly this can't overflow due to previously allocating this
168-
let old_num_bytes = self.cap * elem_size;
169-
170-
// check that the new allocation doesn't exceed `isize::MAX` at all
171-
// regardless of the actual size of the capacity. This combines the
172-
// `new_cap <= isize::MAX` and `new_num_bytes <= usize::MAX` checks
173-
// we need to make. We lose the ability to allocate e.g. 2/3rds of
174-
// the address space with a single Vec of i16's on 32-bit though.
175-
// Alas, poor Yorick -- I knew him, Horatio.
176-
assert!(old_num_bytes <= (isize::MAX as usize) / 2,
177-
"capacity overflow");
178-
179-
let c: NonNull<T> = self.ptr.into();
180-
let ptr = Global.grow(c.cast(),
181-
Layout::array::<T>(self.cap).unwrap(),
182-
Layout::array::<T>(new_cap).unwrap());
183-
(new_cap, ptr)
173+
// This can't overflow since self.cap <= isize::MAX.
174+
let new_cap = 2 * self.cap;
175+
176+
// Layout::array checks that the number of bytes is <= usize::MAX,
177+
// but this is redundant since old_layout.size() <= isize::MAX,
178+
// so the `unwrap` should never fail.
179+
let new_layout = Layout::array::<T>(new_cap).unwrap();
180+
(new_cap, new_layout)
184181
};
185182
186-
// If allocate or reallocate fail, oom
187-
if ptr.is_err() {
188-
handle_alloc_error(Layout::from_size_align_unchecked(
189-
new_cap * elem_size,
190-
mem::align_of::<T>(),
191-
))
192-
}
183+
// Ensure that the new allocation doesn't exceed `isize::MAX` bytes.
184+
assert!(new_layout.size() <= isize::MAX as usize, "Allocation too large");
193185
194-
let ptr = ptr.unwrap();
186+
let new_ptr = if self.cap == 0 {
187+
unsafe { alloc::alloc(new_layout) }
188+
} else {
189+
let old_layout = Layout::array::<T>(self.cap).unwrap();
190+
let old_ptr = self.ptr.as_ptr() as *mut u8;
191+
unsafe { alloc::realloc(old_ptr, old_layout, new_layout.size()) }
192+
};
195193
196-
self.ptr = Unique::new_unchecked(ptr.as_ptr() as *mut _);
194+
// If allocation fails, `new_ptr` will be null, in which case we abort.
195+
self.ptr = match NonNull::new(new_ptr as *mut T) {
196+
Some(p) => p,
197+
None => alloc::handle_alloc_error(new_layout),
198+
};
197199
self.cap = new_cap;
198200
}
199201
}
202+
# fn main() {}
200203
```
201204

202-
Nothing particularly tricky here. Just computing sizes and alignments and doing
203-
some careful multiplication checks.
205+
[Global]: ../std/alloc/struct.Global.html
206+
[handle_alloc_error]: ../alloc/alloc/fn.handle_alloc_error.html
207+
[alloc]: ../alloc/alloc/fn.alloc.html
208+
[realloc]: ../alloc/alloc/fn.realloc.html
209+
[dealloc]: ../alloc/alloc/fn.dealloc.html
210+
[std_alloc]: ../alloc/alloc/index.html

src/vec-dealloc.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,17 @@ ask Rust if `T` `needs_drop` and omit the calls to `pop`. However in practice
77
LLVM is *really* good at removing simple side-effect free code like this, so I
88
wouldn't bother unless you notice it's not being stripped (in this case it is).
99

10-
We must not call `Global.deallocate` when `self.cap == 0`, as in this case we
10+
We must not call `alloc::dealloc` when `self.cap == 0`, as in this case we
1111
haven't actually allocated any memory.
1212

13-
1413
```rust,ignore
1514
impl<T> Drop for Vec<T> {
1615
fn drop(&mut self) {
1716
if self.cap != 0 {
1817
while let Some(_) = self.pop() { }
19-
20-
unsafe {
21-
let c: NonNull<T> = self.ptr.into();
22-
Global.deallocate(c.cast(),
23-
Layout::array::<T>(self.cap).unwrap());
18+
let layout = Layout::array::<T>(self.cap).unwrap();
19+
unsafe {
20+
alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
2421
}
2522
}
2623
}

0 commit comments

Comments
 (0)