Skip to content

Commit 723c57b

Browse files
committed
Adjust Vec to build on stable Rust
1 parent 8551afb commit 723c57b

10 files changed

+316
-297
lines changed

src/vec-alloc.md

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,56 @@
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 is not the same as allocating a zero-sized memory block, which is not allowed by the global allocator (it results in undefined behavior!). So if we can't allocate, but also can't put a null pointer in `ptr`, what do we do in `Vec::new`? Well, we just put some other garbage in there!
75

86
This is perfectly fine because we already have `cap == 0` as our sentinel for no
97
allocation. We don't even need to handle it specially in almost any code because
108
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
9+
Rust value to put here is `mem::align_of::<T>()`. NonNull provides a convenience
10+
for this: `NonNull::dangling()`. There are quite a few places where we'll
1311
want to use `dangling` because there's no real allocation to talk about but
1412
`null` would make the compiler do bad things.
1513

1614
So:
1715

18-
```rust,ignore
16+
```rust
17+
# use std::ptr::NonNull;
18+
# use std::marker::PhantomData;
19+
# use std::mem;
20+
#
21+
# pub struct Vec<T> {
22+
# ptr: NonNull<T>,
23+
# cap: usize,
24+
# len: usize,
25+
# _marker: PhantomData<T>,
26+
# }
27+
#
28+
# unsafe impl<T: Send> Send for Vec<T> {}
29+
# unsafe impl<T: Sync> Sync for Vec<T> {}
1930
impl<T> Vec<T> {
2031
fn new() -> Self {
2132
assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
22-
Vec { ptr: Unique::dangling(), len: 0, cap: 0 }
33+
Vec {
34+
ptr: NonNull::dangling(),
35+
len: 0,
36+
cap: 0,
37+
_marker: PhantomData
38+
}
2339
}
2440
}
41+
# fn main() {}
2542
```
2643

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

3148
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).
49+
that, we use the global allocation functions [`alloc`][alloc], [`realloc`][realloc], and [`dealloc`][dealloc] which are available in stable Rust in [`std::alloc`][std_alloc]. These functions are expected to become deprecated in favor of the methods of [`std::alloc::Global`][Global] after this type is stabilized.
3550

3651
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.
52+
library provides a function [`alloc::handle_alloc_error`][handle_alloc_error],
53+
which will abort the program in a platform-specific manner.
3954
The reason we abort and don't panic is because unwinding can cause allocations
4055
to happen, and that seems like a bad thing to do when your allocator just came
4156
back with "hey I don't have any more memory".
@@ -151,53 +166,59 @@ such we will guard against this case explicitly.
151166

152167
Ok with all the nonsense out of the way, let's actually allocate some memory:
153168

154-
```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)
169+
```rust
170+
use std::alloc::{self, Layout};
171+
use std::marker::PhantomData;
172+
use std::mem;
173+
use std::ptr::NonNull;
174+
175+
struct Vec<T> {
176+
ptr: NonNull<T>,
177+
len: usize,
178+
cap: usize,
179+
_marker: PhantomData<T>,
180+
}
181+
182+
impl<T> Vec<T> {
183+
fn grow(&mut self) {
184+
let (new_cap, new_layout) = if self.cap == 0 {
185+
(1, Layout::array::<T>(1).unwrap())
163186
} 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)
187+
// This can't overflow since self.cap <= isize::MAX.
188+
let new_cap = 2 * self.cap;
189+
190+
// Layout::array checks that the number of bytes is <= usize::MAX,
191+
// but this is redundant since old_layout.size() <= isize::MAX,
192+
// so the `unwrap` should never fail.
193+
let new_layout = Layout::array::<T>(new_cap).unwrap();
194+
(new_cap, new_layout)
184195
};
185196

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-
}
197+
// Ensure that the new allocation doesn't exceed `isize::MAX` bytes.
198+
assert!(new_layout.size() <= isize::MAX as usize, "Allocation too large");
193199

194-
let ptr = ptr.unwrap();
200+
let new_ptr = if self.cap == 0 {
201+
unsafe { alloc::alloc(new_layout) }
202+
} else {
203+
let old_layout = Layout::array::<T>(self.cap).unwrap();
204+
let old_ptr = self.ptr.as_ptr() as *mut u8;
205+
unsafe { alloc::realloc(old_ptr, old_layout, new_layout.size()) }
206+
};
195207

196-
self.ptr = Unique::new_unchecked(ptr.as_ptr() as *mut _);
208+
// If allocation fails, `new_ptr` will be null, in which case we abort.
209+
self.ptr = match NonNull::new(new_ptr as *mut T) {
210+
Some(p) => p,
211+
None => alloc::handle_alloc_error(new_layout),
212+
};
197213
self.cap = new_cap;
198214
}
199215
}
216+
# fn main() {}
200217
```
201218

202-
Nothing particularly tricky here. Just computing sizes and alignments and doing
203-
some careful multiplication checks.
219+
[Global]: ../std/alloc/struct.Global.html
220+
[handle_alloc_error]: ../alloc/alloc/fn.handle_alloc_error.html
221+
[alloc]: ../alloc/alloc/fn.alloc.html
222+
[realloc]: ../alloc/alloc/fn.realloc.html
223+
[dealloc]: ../alloc/alloc/fn.dealloc.html
224+
[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)