Skip to content

Commit 6473bf5

Browse files
committed
Adjust Vec to build on stable Rust
1 parent 7c39c79 commit 6473bf5

File tree

10 files changed

+325
-271
lines changed

10 files changed

+325
-271
lines changed

src/vec-alloc.md

Lines changed: 73 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +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
19-
#![feature(alloc, heap_api)]
20-
21-
use std::mem;
22-
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> {}
2330
impl<T> Vec<T> {
2431
fn new() -> Self {
2532
assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
26-
Vec { ptr: Unique::dangling(), len: 0, cap: 0 }
33+
Vec {
34+
ptr: NonNull::dangling(),
35+
len: 0,
36+
cap: 0,
37+
_marker: PhantomData
38+
}
2739
}
2840
}
41+
# fn main() {}
2942
```
3043

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

3548
Next we need to figure out what to actually do when we *do* want space. For
36-
that, we'll need to use the rest of the heap APIs. These basically allow us to
37-
talk directly to Rust's allocator (jemalloc 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.
3850

3951
We'll also need a way to handle out-of-memory (OOM) conditions. The standard
40-
library calls `std::alloc::oom()`, which in turn calls the `oom` langitem,
41-
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.
4254
The reason we abort and don't panic is because unwinding can cause allocations
4355
to happen, and that seems like a bad thing to do when your allocator just came
4456
back with "hey I don't have any more memory".
@@ -156,52 +168,59 @@ such we will guard against this case explicitly.
156168

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

159-
```rust,ignore
160-
use std::alloc::oom;
171+
```rust
172+
use std::alloc::{self, Layout};
173+
use std::marker::PhantomData;
174+
use std::mem;
175+
use std::ptr::NonNull;
161176

162-
fn grow(&mut self) {
163-
// this is all pretty delicate, so let's say it's all unsafe
164-
unsafe {
165-
// current API requires us to specify size and alignment manually.
166-
let align = mem::align_of::<T>();
167-
let elem_size = mem::size_of::<T>();
177+
struct Vec<T> {
178+
ptr: NonNull<T>,
179+
len: usize,
180+
cap: usize,
181+
_marker: PhantomData<T>,
182+
}
168183

169-
let (new_cap, ptr) = if self.cap == 0 {
170-
let ptr = heap::allocate(elem_size, align);
171-
(1, ptr)
184+
impl<T> Vec<T> {
185+
fn grow(&mut self) {
186+
let (new_cap, new_layout) = if self.cap == 0 {
187+
(1, Layout::array::<T>(1).unwrap())
172188
} else {
173-
// as an invariant, we can assume that `self.cap < isize::MAX`,
174-
// so this doesn't need to be checked.
175-
let new_cap = self.cap * 2;
176-
// Similarly this can't overflow due to previously allocating this
177-
let old_num_bytes = self.cap * elem_size;
178-
179-
// check that the new allocation doesn't exceed `isize::MAX` at all
180-
// regardless of the actual size of the capacity. This combines the
181-
// `new_cap <= isize::MAX` and `new_num_bytes <= usize::MAX` checks
182-
// we need to make. We lose the ability to allocate e.g. 2/3rds of
183-
// the address space with a single Vec of i16's on 32-bit though.
184-
// Alas, poor Yorick -- I knew him, Horatio.
185-
assert!(old_num_bytes <= (isize::MAX as usize) / 2,
186-
"capacity overflow");
187-
188-
let new_num_bytes = old_num_bytes * 2;
189-
let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _,
190-
old_num_bytes,
191-
new_num_bytes,
192-
align);
193-
(new_cap, ptr)
189+
// This can't overflow since self.cap <= isize::MAX.
190+
let new_cap = 2 * self.cap;
191+
192+
// Layout::array checks that the number of bytes is <= usize::MAX,
193+
// but this is redundant since old_layout.size() <= isize::MAX,
194+
// so the `unwrap` should never fail.
195+
let new_layout = Layout::array::<T>(new_cap).unwrap();
196+
(new_cap, new_layout)
194197
};
195198

196-
// If allocate or reallocate fail, we'll get `null` back
197-
if ptr.is_null() { oom(); }
199+
// Ensure that the new allocation doesn't exceed `isize::MAX` bytes.
200+
assert!(new_layout.size() <= isize::MAX as usize, "Allocation too large");
201+
202+
let new_ptr = if self.cap == 0 {
203+
unsafe { alloc::alloc(new_layout) }
204+
} else {
205+
let old_layout = Layout::array::<T>(self.cap).unwrap();
206+
let old_ptr = self.ptr.as_ptr() as *mut u8;
207+
unsafe { alloc::realloc(old_ptr, old_layout, new_layout.size()) }
208+
};
198209

199-
self.ptr = Unique::new(ptr as *mut _);
210+
// If allocation fails, `new_ptr` will be null, in which case we abort.
211+
self.ptr = match NonNull::new(new_ptr as *mut T) {
212+
Some(p) => p,
213+
None => alloc::handle_alloc_error(new_layout),
214+
};
200215
self.cap = new_cap;
201216
}
202217
}
218+
# fn main() {}
203219
```
204220

205-
Nothing particularly tricky here. Just computing sizes and alignments and doing
206-
some careful multiplication checks.
207-
221+
[Global]: ../std/alloc/struct.Global.html
222+
[handle_alloc_error]: ../alloc/alloc/fn.handle_alloc_error.html
223+
[alloc]: ../alloc/alloc/fn.alloc.html
224+
[realloc]: ../alloc/alloc/fn.realloc.html
225+
[dealloc]: ../alloc/alloc/fn.dealloc.html
226+
[std_alloc]: ../alloc/alloc/index.html

src/vec-dealloc.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@ impl<T> Drop for Vec<T> {
1616
fn drop(&mut self) {
1717
if self.cap != 0 {
1818
while let Some(_) = self.pop() { }
19-
20-
let align = mem::align_of::<T>();
21-
let elem_size = mem::size_of::<T>();
22-
let num_bytes = elem_size * self.cap;
23-
unsafe {
24-
heap::deallocate(self.ptr.as_ptr() as *mut _, num_bytes, align);
19+
let layout = Layout::array::<T>(self.cap).unwrap();
20+
unsafe {
21+
alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
2522
}
2623
}
2724
}

0 commit comments

Comments
 (0)