Update vector code examples

The code samples were different from the final code and included
deprecated API calls.
pull/255/head
Lukas Hettwer 4 years ago committed by Alexis Beingessner
parent a3180eb461
commit a8584998ea

@ -16,10 +16,6 @@ want to use `dangling` because there's no real allocation to talk about but
So: So:
```rust,ignore ```rust,ignore
#![feature(alloc, heap_api)]
use std::mem;
impl<T> Vec<T> { impl<T> Vec<T> {
fn new() -> Self { fn new() -> Self {
assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs"); assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
@ -76,9 +72,7 @@ compiler to be able to reason about data dependencies and aliasing.
As a simple example, consider the following fragment of code: As a simple example, consider the following fragment of code:
```rust ```rust,ignore
# let x = &mut 0;
# let y = &mut 0;
*x *= 7; *x *= 7;
*y *= 3; *y *= 3;
``` ```
@ -158,22 +152,18 @@ such we will guard against this case explicitly.
Ok with all the nonsense out of the way, let's actually allocate some memory: Ok with all the nonsense out of the way, let's actually allocate some memory:
```rust,ignore ```rust,ignore
use std::alloc::oom;
fn grow(&mut self) { fn grow(&mut self) {
// this is all pretty delicate, so let's say it's all unsafe // this is all pretty delicate, so let's say it's all unsafe
unsafe { unsafe {
// current API requires us to specify size and alignment manually.
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>(); let elem_size = mem::size_of::<T>();
let (new_cap, ptr) = if self.cap == 0 { let (new_cap, ptr) = if self.cap == 0 {
let ptr = heap::allocate(elem_size, align); let ptr = Global.allocate(Layout::array::<T>(1).unwrap());
(1, ptr) (1, ptr)
} else { } else {
// as an invariant, we can assume that `self.cap < isize::MAX`, // as an invariant, we can assume that `self.cap < isize::MAX`,
// so this doesn't need to be checked. // so this doesn't need to be checked.
let new_cap = self.cap * 2; let new_cap = 2 * self.cap;
// Similarly this can't overflow due to previously allocating this // Similarly this can't overflow due to previously allocating this
let old_num_bytes = self.cap * elem_size; let old_num_bytes = self.cap * elem_size;
@ -186,18 +176,24 @@ fn grow(&mut self) {
assert!(old_num_bytes <= (isize::MAX as usize) / 2, assert!(old_num_bytes <= (isize::MAX as usize) / 2,
"capacity overflow"); "capacity overflow");
let new_num_bytes = old_num_bytes * 2; let c: NonNull<T> = self.ptr.into();
let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _, let ptr = Global.grow(c.cast(),
old_num_bytes, Layout::array::<T>(self.cap).unwrap(),
new_num_bytes, Layout::array::<T>(new_cap).unwrap());
align);
(new_cap, ptr) (new_cap, ptr)
}; };
// If allocate or reallocate fail, we'll get `null` back // If allocate or reallocate fail, oom
if ptr.is_null() { oom(); } if ptr.is_err() {
handle_alloc_error(Layout::from_size_align_unchecked(
new_cap * elem_size,
mem::align_of::<T>(),
))
}
let ptr = ptr.unwrap();
self.ptr = Unique::new(ptr as *mut _); self.ptr = Unique::new_unchecked(ptr.as_ptr() as *mut _);
self.cap = new_cap; self.cap = new_cap;
} }
} }
@ -205,4 +201,3 @@ fn grow(&mut self) {
Nothing particularly tricky here. Just computing sizes and alignments and doing Nothing particularly tricky here. Just computing sizes and alignments and doing
some careful multiplication checks. some careful multiplication checks.

@ -7,7 +7,7 @@ ask Rust if `T` `needs_drop` and omit the calls to `pop`. However in practice
LLVM is *really* good at removing simple side-effect free code like this, so I LLVM is *really* good at removing simple side-effect free code like this, so I
wouldn't bother unless you notice it's not being stripped (in this case it is). wouldn't bother unless you notice it's not being stripped (in this case it is).
We must not call `heap::deallocate` when `self.cap == 0`, as in this case we We must not call `Global.deallocate` when `self.cap == 0`, as in this case we
haven't actually allocated any memory. haven't actually allocated any memory.
@ -17,11 +17,10 @@ impl<T> Drop for Vec<T> {
if self.cap != 0 { if self.cap != 0 {
while let Some(_) = self.pop() { } while let Some(_) = self.pop() { }
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>();
let num_bytes = elem_size * self.cap;
unsafe { unsafe {
heap::deallocate(self.ptr.as_ptr() as *mut _, num_bytes, align); let c: NonNull<T> = self.ptr.into();
Global.deallocate(c.cast(),
Layout::array::<T>(self.cap).unwrap());
} }
} }
} }

@ -26,7 +26,7 @@ impl<'a, T> Iterator for Drain<'a, T> {
-- wait, this is seeming familiar. Let's do some more compression. Both -- wait, this is seeming familiar. Let's do some more compression. Both
IntoIter and Drain have the exact same structure, let's just factor it out. IntoIter and Drain have the exact same structure, let's just factor it out.
```rust ```rust,ignore
struct RawValIter<T> { struct RawValIter<T> {
start: *const T, start: *const T,
end: *const T, end: *const T,
@ -75,7 +75,7 @@ impl<T> DoubleEndedIterator for IntoIter<T> {
impl<T> Drop for IntoIter<T> { impl<T> Drop for IntoIter<T> {
fn drop(&mut self) { fn drop(&mut self) {
for _ in &mut self.iter {} for _ in &mut *self {}
} }
} }
@ -123,7 +123,7 @@ impl<'a, T> DoubleEndedIterator for Drain<'a, T> {
impl<'a, T> Drop for Drain<'a, T> { impl<'a, T> Drop for Drain<'a, T> {
fn drop(&mut self) { fn drop(&mut self) {
for _ in &mut self.iter {} for _ in &mut *self {}
} }
} }

@ -174,7 +174,7 @@ impl<T> Vec<T> {
impl<T> Drop for Vec<T> { impl<T> Drop for Vec<T> {
fn drop(&mut self) { fn drop(&mut self) {
while let Some(_) = self.pop() {} while let Some(_) = self.pop() {}
// allocation is handled by RawVec // deallocation is handled by RawVec
} }
} }
@ -214,7 +214,7 @@ impl<T> RawValIter<T> {
slice.as_ptr() slice.as_ptr()
} else { } else {
slice.as_ptr().offset(slice.len() as isize) slice.as_ptr().offset(slice.len() as isize)
} },
} }
} }
} }
@ -307,10 +307,10 @@ impl<'a, T> DoubleEndedIterator for Drain<'a, T> {
impl<'a, T> Drop for Drain<'a, T> { impl<'a, T> Drop for Drain<'a, T> {
fn drop(&mut self) { fn drop(&mut self) {
// pre-drain the iter // pre-drain the iter
for _ in &mut self.iter {} for _ in &mut *self {}
} }
} }
#
# fn main() { # fn main() {
# tests::create_push_pop(); # tests::create_push_pop();
# tests::iter_test(); # tests::iter_test();
@ -318,7 +318,7 @@ impl<'a, T> Drop for Drain<'a, T> {
# tests::test_zst(); # tests::test_zst();
# println!("All tests finished OK"); # println!("All tests finished OK");
# } # }
#
# mod tests { # mod tests {
# use super::*; # use super::*;
# pub fn create_push_pop() { # pub fn create_push_pop() {

@ -22,11 +22,11 @@ pub fn insert(&mut self, index: usize, elem: T) {
unsafe { unsafe {
if index < self.len { if index < self.len {
// ptr::copy(src, dest, len): "copy from source to dest len elems" // ptr::copy(src, dest, len): "copy from source to dest len elems"
ptr::copy(self.ptr.offset(index as isize), ptr::copy(self.ptr.as_ptr().offset(index as isize),
self.ptr.offset(index as isize + 1), self.ptr.as_ptr().offset(index as isize + 1),
self.len - index); self.len - index);
} }
ptr::write(self.ptr.offset(index as isize), elem); ptr::write(self.ptr.as_ptr().offset(index as isize), elem);
self.len += 1; self.len += 1;
} }
} }
@ -41,9 +41,9 @@ pub fn remove(&mut self, index: usize) -> T {
assert!(index < self.len, "index out of bounds"); assert!(index < self.len, "index out of bounds");
unsafe { unsafe {
self.len -= 1; self.len -= 1;
let result = ptr::read(self.ptr.offset(index as isize)); let result = ptr::read(self.ptr.as_ptr().offset(index as isize));
ptr::copy(self.ptr.offset(index as isize + 1), ptr::copy(self.ptr.as_ptr().offset(index as isize + 1),
self.ptr.offset(index as isize), self.ptr.as_ptr().offset(index as isize),
self.len - index); self.len - index);
result result
} }

@ -43,7 +43,7 @@ dropped.
So we're going to use the following struct: So we're going to use the following struct:
```rust,ignore ```rust,ignore
struct IntoIter<T> { pub struct IntoIter<T> {
buf: Unique<T>, buf: Unique<T>,
cap: usize, cap: usize,
start: *const T, start: *const T,
@ -55,7 +55,7 @@ And this is what we end up with for initialization:
```rust,ignore ```rust,ignore
impl<T> Vec<T> { impl<T> Vec<T> {
fn into_iter(self) -> IntoIter<T> { pub fn into_iter(self) -> IntoIter<T> {
// Can't destructure Vec since it's Drop // Can't destructure Vec since it's Drop
let ptr = self.ptr; let ptr = self.ptr;
let cap = self.cap; let cap = self.cap;
@ -68,13 +68,13 @@ impl<T> Vec<T> {
IntoIter { IntoIter {
buf: ptr, buf: ptr,
cap: cap, cap: cap,
start: *ptr, start: ptr.as_ptr(),
end: if cap == 0 { end: if cap == 0 {
// can't offset off this pointer, it's not allocated! // can't offset off this pointer, it's not allocated!
*ptr ptr.as_ptr()
} else { } else {
ptr.offset(len as isize) ptr.as_ptr().offset(len as isize)
} },
} }
} }
} }
@ -135,11 +135,10 @@ impl<T> Drop for IntoIter<T> {
// drop any remaining elements // drop any remaining elements
for _ in &mut *self {} for _ in &mut *self {}
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>();
let num_bytes = elem_size * self.cap;
unsafe { unsafe {
heap::deallocate(self.buf.as_ptr() as *mut _, num_bytes, align); let c: NonNull<T> = self.buf.into();
Global.deallocate(c.cast(),
Layout::array::<T>(self.cap).unwrap());
} }
} }
} }

@ -6,13 +6,12 @@ elements that have been initialized.
Naively, this means we just want this design: Naively, this means we just want this design:
```rust ```rust,ignore
pub struct Vec<T> { pub struct Vec<T> {
ptr: *mut T, ptr: *mut T,
cap: usize, cap: usize,
len: usize, len: usize,
} }
# fn main() {}
``` ```
And indeed this would compile. Unfortunately, it would be incorrect. First, the And indeed this would compile. Unfortunately, it would be incorrect. First, the
@ -37,7 +36,7 @@ As a recap, Unique is a wrapper around a raw pointer that declares that:
We can implement all of the above requirements except for the last We can implement all of the above requirements except for the last
one in stable Rust: one in stable Rust:
```rust ```rust,ignore
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use std::mem; use std::mem;
@ -61,8 +60,6 @@ impl<T> Unique<T> {
self.ptr as *mut T self.ptr as *mut T
} }
} }
# fn main() {}
``` ```
Unfortunately the mechanism for stating that your value is non-zero is Unfortunately the mechanism for stating that your value is non-zero is
@ -70,18 +67,12 @@ unstable and unlikely to be stabilized soon. As such we're just going to
take the hit and use std's Unique: take the hit and use std's Unique:
```rust ```rust,ignore
#![feature(ptr_internals)]
use std::ptr::{Unique, self};
pub struct Vec<T> { pub struct Vec<T> {
ptr: Unique<T>, ptr: Unique<T>,
cap: usize, cap: usize,
len: usize, len: usize,
} }
# fn main() {}
``` ```
If you don't care about the null-pointer optimization, then you can use the If you don't care about the null-pointer optimization, then you can use the

@ -22,7 +22,7 @@ pub fn push(&mut self, elem: T) {
if self.len == self.cap { self.grow(); } if self.len == self.cap { self.grow(); }
unsafe { unsafe {
ptr::write(self.ptr.offset(self.len as isize), elem); ptr::write(self.ptr.as_ptr().offset(self.len as isize), elem);
} }
// Can't fail, we'll OOM first. // Can't fail, we'll OOM first.
@ -48,7 +48,7 @@ pub fn pop(&mut self) -> Option<T> {
} else { } else {
self.len -= 1; self.len -= 1;
unsafe { unsafe {
Some(ptr::read(self.ptr.offset(self.len as isize))) Some(ptr::read(self.ptr.as_ptr().offset(self.len as isize)))
} }
} }
} }

@ -1,4 +1,3 @@
# RawVec # RawVec
We've actually reached an interesting situation here: we've duplicated the logic We've actually reached an interesting situation here: we've duplicated the logic
@ -17,46 +16,50 @@ struct RawVec<T> {
impl<T> RawVec<T> { impl<T> RawVec<T> {
fn new() -> Self { fn new() -> Self {
assert!(mem::size_of::<T>() != 0, "TODO: implement ZST support"); assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
RawVec { ptr: Unique::dangling(), cap: 0 } RawVec { ptr: Unique::dangling(), cap: 0 }
} }
// unchanged from Vec // unchanged from Vec
fn grow(&mut self) { fn grow(&mut self) {
unsafe { unsafe {
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>(); let elem_size = mem::size_of::<T>();
let (new_cap, ptr) = if self.cap == 0 { let (new_cap, ptr) = if self.cap == 0 {
let ptr = heap::allocate(elem_size, align); let ptr = Global.allocate(Layout::array::<T>(1).unwrap());
(1, ptr) (1, ptr)
} else { } else {
let new_cap = 2 * self.cap; let new_cap = 2 * self.cap;
let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _, let c: NonNull<T> = self.ptr.into();
self.cap * elem_size, let ptr = Global.grow(c.cast(),
new_cap * elem_size, Layout::array::<T>(self.cap).unwrap(),
align); Layout::array::<T>(new_cap).unwrap());
(new_cap, ptr) (new_cap, ptr)
}; };
// If allocate or reallocate fail, we'll get `null` back // If allocate or reallocate fail, oom
if ptr.is_null() { oom() } if ptr.is_err() {
handle_alloc_error(Layout::from_size_align_unchecked(
new_cap * elem_size,
mem::align_of::<T>(),
))
}
let ptr = ptr.unwrap();
self.ptr = Unique::new(ptr as *mut _); self.ptr = Unique::new_unchecked(ptr.as_ptr() as *mut _);
self.cap = new_cap; self.cap = new_cap;
} }
} }
} }
impl<T> Drop for RawVec<T> { impl<T> Drop for RawVec<T> {
fn drop(&mut self) { fn drop(&mut self) {
if self.cap != 0 { if self.cap != 0 {
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>();
let num_bytes = elem_size * self.cap;
unsafe { unsafe {
heap::deallocate(self.ptr.as_mut() as *mut _, num_bytes, align); let c: NonNull<T> = self.ptr.into();
Global.deallocate(c.cast(),
Layout::array::<T>(self.cap).unwrap());
} }
} }
} }
@ -81,7 +84,7 @@ impl<T> Vec<T> {
} }
// push/pop/insert/remove largely unchanged: // push/pop/insert/remove largely unchanged:
// * `self.ptr -> self.ptr()` // * `self.ptr.as_ptr() -> self.ptr()`
// * `self.cap -> self.cap()` // * `self.cap -> self.cap()`
// * `self.grow -> self.buf.grow()` // * `self.grow -> self.buf.grow()`
} }
@ -97,7 +100,7 @@ impl<T> Drop for Vec<T> {
And finally we can really simplify IntoIter: And finally we can really simplify IntoIter:
```rust,ignore ```rust,ignore
struct IntoIter<T> { pub struct IntoIter<T> {
_buf: RawVec<T>, // we don't actually care about this. Just need it to live. _buf: RawVec<T>, // we don't actually care about this. Just need it to live.
start: *const T, start: *const T,
end: *const T, end: *const T,
@ -123,8 +126,8 @@ impl<T> Vec<T> {
mem::forget(self); mem::forget(self);
IntoIter { IntoIter {
start: *buf.ptr, start: buf.ptr.as_ptr(),
end: buf.ptr.offset(len as isize), end: buf.ptr.as_ptr().offset(len as isize),
_buf: buf, _buf: buf,
} }
} }

@ -11,7 +11,7 @@ zero-sized types. We need to be careful of two things:
C-style pointer iterator. C-style pointer iterator.
Thankfully we abstracted out pointer-iterators and allocating handling into Thankfully we abstracted out pointer-iterators and allocating handling into
RawValIter and RawVec respectively. How mysteriously convenient. `RawValIter` and `RawVec` respectively. How mysteriously convenient.
@ -30,7 +30,7 @@ no longer valid with zero-sized types. We must explicitly guard against capacity
overflow for zero-sized types. overflow for zero-sized types.
Due to our current architecture, all this means is writing 3 guards, one in each Due to our current architecture, all this means is writing 3 guards, one in each
method of RawVec. method of `RawVec`.
```rust,ignore ```rust,ignore
impl<T> RawVec<T> { impl<T> RawVec<T> {
@ -50,24 +50,29 @@ impl<T> RawVec<T> {
// 0, getting to here necessarily means the Vec is overfull. // 0, getting to here necessarily means the Vec is overfull.
assert!(elem_size != 0, "capacity overflow"); assert!(elem_size != 0, "capacity overflow");
let align = mem::align_of::<T>();
let (new_cap, ptr) = if self.cap == 0 { let (new_cap, ptr) = if self.cap == 0 {
let ptr = heap::allocate(elem_size, align); let ptr = Global.allocate(Layout::array::<T>(1).unwrap());
(1, ptr) (1, ptr)
} else { } else {
let new_cap = 2 * self.cap; let new_cap = 2 * self.cap;
let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _, let c: NonNull<T> = self.ptr.into();
self.cap * elem_size, let ptr = Global.grow(c.cast(),
new_cap * elem_size, Layout::array::<T>(self.cap).unwrap(),
align); Layout::array::<T>(new_cap).unwrap());
(new_cap, ptr) (new_cap, ptr)
}; };
// If allocate or reallocate fail, we'll get `null` back // If allocate or reallocate fail, oom
if ptr.is_null() { oom() } if ptr.is_err() {
handle_alloc_error(Layout::from_size_align_unchecked(
new_cap * elem_size,
mem::align_of::<T>(),
))
}
let ptr = ptr.unwrap();
self.ptr = Unique::new(ptr as *mut _); self.ptr = Unique::new_unchecked(ptr.as_ptr() as *mut _);
self.cap = new_cap; self.cap = new_cap;
} }
} }
@ -79,11 +84,10 @@ impl<T> Drop for RawVec<T> {
// don't free zero-sized allocations, as they were never allocated. // don't free zero-sized allocations, as they were never allocated.
if self.cap != 0 && elem_size != 0 { if self.cap != 0 && elem_size != 0 {
let align = mem::align_of::<T>();
let num_bytes = elem_size * self.cap;
unsafe { unsafe {
heap::deallocate(self.ptr.as_ptr() as *mut _, num_bytes, align); let c: NonNull<T> = self.ptr.into();
Global.deallocate(c.cast(),
Layout::array::<T>(self.cap).unwrap());
} }
} }
} }
@ -114,7 +118,7 @@ impl<T> RawValIter<T> {
slice.as_ptr() slice.as_ptr()
} else { } else {
slice.as_ptr().offset(slice.len() as isize) slice.as_ptr().offset(slice.len() as isize)
} },
} }
} }
} }

Loading…
Cancel
Save