Skip to content

Commit e82dfe2

Browse files
committed
Adjust Vec to build on stable Rust
1 parent bfe1ab9 commit e82dfe2

File tree

10 files changed

+247
-269
lines changed

10 files changed

+247
-269
lines changed

src/vec-alloc.md

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,34 @@
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
19-
#![feature(alloc, heap_api)]
20-
2121
use std::mem;
2222
2323
impl<T> Vec<T> {
2424
fn new() -> Self {
2525
assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
26-
Vec { ptr: Unique::dangling(), len: 0, cap: 0 }
26+
Vec {
27+
ptr: NonNull::dangling(),
28+
len: 0,
29+
cap: 0,
30+
_marker: PhantomData
31+
}
2732
}
2833
}
2934
```
@@ -32,13 +37,15 @@ I slipped in that assert there because zero-sized types will require some
3237
special handling throughout our code, and I want to defer the issue for now.
3338
Without this assert, some of our early drafts will do some Very Bad Things.
3439

35-
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).
40+
Next we need to figure out what to actually do when we *do* want space. For that,
41+
we use the global allocation functions [`alloc`][alloc], [`realloc`][realloc],
42+
and [`dealloc`][dealloc] which are available in stable Rust in
43+
[`std::alloc`][std_alloc]. These functions are expected to become deprecated in
44+
favor of the methods of [`std::alloc::Global`][Global] after this type is stabilized.
3845

3946
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.
47+
library provides a function [`alloc::handle_alloc_error`][handle_alloc_error],
48+
which will abort the program in a platform-specific manner.
4249
The reason we abort and don't panic is because unwinding can cause allocations
4350
to happen, and that seems like a bad thing to do when your allocator just came
4451
back with "hey I don't have any more memory".
@@ -157,51 +164,48 @@ such we will guard against this case explicitly.
157164
Ok with all the nonsense out of the way, let's actually allocate some memory:
158165

159166
```rust,ignore
160-
use std::alloc::oom;
161-
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>();
168-
169-
let (new_cap, ptr) = if self.cap == 0 {
170-
let ptr = heap::allocate(elem_size, align);
171-
(1, ptr)
167+
use std::alloc::{self, Layout};
168+
169+
impl<T> Vec<T> {
170+
fn grow(&mut self) {
171+
let (new_cap, new_layout) = if self.cap == 0 {
172+
(1, Layout::array::<T>(1).unwrap())
172173
} 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)
174+
// This can't overflow since self.cap <= isize::MAX.
175+
let new_cap = 2 * self.cap;
176+
177+
// Layout::array checks that the number of bytes is <= usize::MAX,
178+
// but this is redundant since old_layout.size() <= isize::MAX,
179+
// so the `unwrap` should never fail.
180+
let new_layout = Layout::array::<T>(new_cap).unwrap();
181+
(new_cap, new_layout)
194182
};
195183
196-
// If allocate or reallocate fail, we'll get `null` back
197-
if ptr.is_null() { oom(); }
184+
// Ensure that the new allocation doesn't exceed `isize::MAX` bytes.
185+
assert!(new_layout.size() <= isize::MAX as usize, "Allocation too large");
186+
187+
let new_ptr = if self.cap == 0 {
188+
unsafe { alloc::alloc(new_layout) }
189+
} else {
190+
let old_layout = Layout::array::<T>(self.cap).unwrap();
191+
let old_ptr = self.ptr.as_ptr() as *mut u8;
192+
unsafe { alloc::realloc(old_ptr, old_layout, new_layout.size()) }
193+
};
198194
199-
self.ptr = Unique::new(ptr as *mut _);
195+
// If allocation fails, `new_ptr` will be null, in which case we abort.
196+
self.ptr = match NonNull::new(new_ptr as *mut T) {
197+
Some(p) => p,
198+
None => alloc::handle_alloc_error(new_layout),
199+
};
200200
self.cap = new_cap;
201201
}
202202
}
203+
# fn main() {}
203204
```
204205

205-
Nothing particularly tricky here. Just computing sizes and alignments and doing
206-
some careful multiplication checks.
207-
206+
[Global]: ../std/alloc/struct.Global.html
207+
[handle_alloc_error]: ../alloc/alloc/fn.handle_alloc_error.html
208+
[alloc]: ../alloc/alloc/fn.alloc.html
209+
[realloc]: ../alloc/alloc/fn.realloc.html
210+
[dealloc]: ../alloc/alloc/fn.dealloc.html
211+
[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
}

src/vec-final.md

Lines changed: 59 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,72 @@
11
# The Final Code
22

33
```rust
4-
#![feature(ptr_internals)]
5-
#![feature(allocator_api)]
6-
#![feature(alloc_layout_extra)]
7-
8-
use std::ptr::{Unique, NonNull, self};
4+
use std::alloc::{self, Layout};
5+
use std::marker::PhantomData;
96
use std::mem;
107
use std::ops::{Deref, DerefMut};
11-
use std::marker::PhantomData;
12-
use std::alloc::{
13-
AllocInit,
14-
AllocRef,
15-
Global,
16-
GlobalAlloc,
17-
Layout,
18-
ReallocPlacement,
19-
handle_alloc_error
20-
};
8+
use std::ptr::{self, NonNull};
219

2210
struct RawVec<T> {
23-
ptr: Unique<T>,
11+
ptr: NonNull<T>,
2412
cap: usize,
13+
_marker: PhantomData<T>,
2514
}
2615

16+
unsafe impl<T: Send> Send for RawVec<T> {}
17+
unsafe impl<T: Sync> Sync for RawVec<T> {}
18+
2719
impl<T> RawVec<T> {
2820
fn new() -> Self {
2921
// !0 is usize::MAX. This branch should be stripped at compile time.
3022
let cap = if mem::size_of::<T>() == 0 { !0 } else { 0 };
3123

32-
// Unique::dangling() doubles as "unallocated" and "zero-sized allocation"
33-
RawVec { ptr: Unique::dangling(), cap: cap }
24+
// NonNull::dangling() doubles as "unallocated" and "zero-sized allocation"
25+
RawVec {
26+
ptr: NonNull::dangling(),
27+
cap: cap,
28+
_marker: PhantomData,
29+
}
3430
}
3531

3632
fn grow(&mut self) {
37-
unsafe {
38-
let elem_size = mem::size_of::<T>();
33+
// since we set the capacity to usize::MAX when T has size 0,
34+
// getting to here necessarily means the Vec is overfull.
35+
assert!(mem::size_of::<T>() != 0, "capacity overflow");
3936

40-
// since we set the capacity to usize::MAX when elem_size is
41-
// 0, getting to here necessarily means the Vec is overfull.
42-
assert!(elem_size != 0, "capacity overflow");
43-
44-
let (new_cap, ptr) = if self.cap == 0 {
45-
let ptr = Global.alloc(Layout::array::<T>(1).unwrap(), AllocInit::Uninitialized);
46-
(1, ptr)
47-
} else {
48-
let new_cap = 2 * self.cap;
49-
let c: NonNull<T> = self.ptr.into();
50-
let ptr = Global.grow(c.cast(),
51-
Layout::array::<T>(self.cap).unwrap(),
52-
Layout::array::<T>(new_cap).unwrap().size(),
53-
ReallocPlacement::MayMove,
54-
AllocInit::Uninitialized);
55-
(new_cap, ptr)
56-
};
57-
58-
// If allocate or reallocate fail, oom
59-
if ptr.is_err() {
60-
handle_alloc_error(Layout::from_size_align_unchecked(
61-
new_cap * elem_size,
62-
mem::align_of::<T>(),
63-
))
64-
}
65-
let ptr = ptr.unwrap().ptr;
66-
67-
self.ptr = Unique::new_unchecked(ptr.as_ptr() as *mut _);
68-
self.cap = new_cap;
69-
}
37+
let (new_cap, new_layout) = if self.cap == 0 {
38+
(1, Layout::array::<T>(1).unwrap())
39+
} else {
40+
// This can't overflow because we ensure self.cap <= isize::MAX.
41+
let new_cap = 2 * self.cap;
42+
43+
// Layout::array checks that the number of bytes is <= usize::MAX,
44+
// but this is redundant since old_layout.size() <= isize::MAX,
45+
// so the `unwrap` should never fail.
46+
let new_layout = Layout::array::<T>(new_cap).unwrap();
47+
(new_cap, new_layout)
48+
};
49+
50+
// Ensure that the new allocation doesn't exceed `isize::MAX` bytes.
51+
assert!(
52+
new_layout.size() <= isize::MAX as usize,
53+
"Allocation too large"
54+
);
55+
56+
let new_ptr = if self.cap == 0 {
57+
unsafe { alloc::alloc(new_layout) }
58+
} else {
59+
let old_layout = Layout::array::<T>(self.cap).unwrap();
60+
let old_ptr = self.ptr.as_ptr() as *mut u8;
61+
unsafe { alloc::realloc(old_ptr, old_layout, new_layout.size()) }
62+
};
63+
64+
// If allocation fails, `new_ptr` will be null, in which case we abort.
65+
self.ptr = match NonNull::new(new_ptr as *mut T) {
66+
Some(p) => p,
67+
None => alloc::handle_alloc_error(new_layout),
68+
};
69+
self.cap = new_cap;
7070
}
7171
}
7272

@@ -75,9 +75,10 @@ impl<T> Drop for RawVec<T> {
7575
let elem_size = mem::size_of::<T>();
7676
if self.cap != 0 && elem_size != 0 {
7777
unsafe {
78-
let c: NonNull<T> = self.ptr.into();
79-
Global.dealloc(c.cast(),
80-
Layout::array::<T>(self.cap).unwrap());
78+
alloc::dealloc(
79+
self.ptr.as_ptr() as *mut u8,
80+
Layout::array::<T>(self.cap).unwrap(),
81+
);
8182
}
8283
}
8384
}
@@ -94,7 +95,10 @@ impl<T> Vec<T> {
9495
fn cap(&self) -> usize { self.buf.cap }
9596

9697
pub fn new() -> Self {
97-
Vec { buf: RawVec::new(), len: 0 }
98+
Vec {
99+
buf: RawVec::new(),
100+
len: 0,
101+
}
98102
}
99103
pub fn push(&mut self, elem: T) {
100104
if self.len == self.cap() { self.buf.grow(); }
@@ -103,7 +107,7 @@ impl<T> Vec<T> {
103107
ptr::write(self.ptr().offset(self.len as isize), elem);
104108
}
105109

106-
// Can't fail, we'll OOM first.
110+
// Can't overflow, we'll OOM first.
107111
self.len += 1;
108112
}
109113

@@ -199,10 +203,6 @@ impl<T> DerefMut for Vec<T> {
199203
}
200204
}
201205

202-
203-
204-
205-
206206
struct RawValIter<T> {
207207
start: *const T,
208208
end: *const T,
@@ -266,9 +266,6 @@ impl<T> DoubleEndedIterator for RawValIter<T> {
266266
}
267267
}
268268

269-
270-
271-
272269
pub struct IntoIter<T> {
273270
_buf: RawVec<T>, // we don't actually care about this. Just need it to live.
274271
iter: RawValIter<T>,
@@ -290,9 +287,6 @@ impl<T> Drop for IntoIter<T> {
290287
}
291288
}
292289

293-
294-
295-
296290
pub struct Drain<'a, T: 'a> {
297291
vec: PhantomData<&'a mut Vec<T>>,
298292
iter: RawValIter<T>,

src/vec-insert-remove.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ pub fn insert(&mut self, index: usize, elem: T) {
2222
unsafe {
2323
if index < self.len {
2424
// ptr::copy(src, dest, len): "copy from source to dest len elems"
25-
ptr::copy(self.ptr.offset(index as isize),
26-
self.ptr.offset(index as isize + 1),
25+
ptr::copy(self.ptr.as_ptr().offset(index as isize),
26+
self.ptr.as_ptr().offset(index as isize + 1),
2727
self.len - index);
2828
}
29-
ptr::write(self.ptr.offset(index as isize), elem);
29+
ptr::write(self.ptr.as_ptr().offset(index as isize), elem);
3030
self.len += 1;
3131
}
3232
}
@@ -41,9 +41,9 @@ pub fn remove(&mut self, index: usize) -> T {
4141
assert!(index < self.len, "index out of bounds");
4242
unsafe {
4343
self.len -= 1;
44-
let result = ptr::read(self.ptr.offset(index as isize));
45-
ptr::copy(self.ptr.offset(index as isize + 1),
46-
self.ptr.offset(index as isize),
44+
let result = ptr::read(self.ptr.as_ptr().offset(index as isize));
45+
ptr::copy(self.ptr.as_ptr().offset(index as isize + 1),
46+
self.ptr.as_ptr().offset(index as isize),
4747
self.len - index);
4848
result
4949
}

0 commit comments

Comments
 (0)