From a8584998eacdea7106a1dfafcbf6c1c06fcdf925 Mon Sep 17 00:00:00 2001 From: Lukas Hettwer Date: Tue, 29 Dec 2020 19:12:14 +0100 Subject: [PATCH] Update vector code examples The code samples were different from the final code and included deprecated API calls. --- src/vec-alloc.md | 39 ++++++++++++++++-------------------- src/vec-dealloc.md | 9 ++++----- src/vec-drain.md | 6 +++--- src/vec-final.md | 10 +++++----- src/vec-insert-remove.md | 12 +++++------ src/vec-into-iter.md | 19 +++++++++--------- src/vec-layout.md | 15 +++----------- src/vec-push-pop.md | 4 ++-- src/vec-raw.md | 43 +++++++++++++++++++++------------------- src/vec-zsts.md | 38 +++++++++++++++++++---------------- 10 files changed, 93 insertions(+), 102 deletions(-) diff --git a/src/vec-alloc.md b/src/vec-alloc.md index 85a1197..0d27057 100644 --- a/src/vec-alloc.md +++ b/src/vec-alloc.md @@ -16,10 +16,6 @@ want to use `dangling` because there's no real allocation to talk about but So: ```rust,ignore -#![feature(alloc, heap_api)] - -use std::mem; - impl Vec { fn new() -> Self { assert!(mem::size_of::() != 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: -```rust -# let x = &mut 0; -# let y = &mut 0; +```rust,ignore *x *= 7; *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: ```rust,ignore -use std::alloc::oom; - fn grow(&mut self) { // this is all pretty delicate, so let's say it's all unsafe unsafe { - // current API requires us to specify size and alignment manually. - let align = mem::align_of::(); let elem_size = mem::size_of::(); let (new_cap, ptr) = if self.cap == 0 { - let ptr = heap::allocate(elem_size, align); + let ptr = Global.allocate(Layout::array::(1).unwrap()); (1, ptr) } else { // as an invariant, we can assume that `self.cap < isize::MAX`, // 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 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, "capacity overflow"); - let new_num_bytes = old_num_bytes * 2; - let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _, - old_num_bytes, - new_num_bytes, - align); + let c: NonNull = self.ptr.into(); + let ptr = Global.grow(c.cast(), + Layout::array::(self.cap).unwrap(), + Layout::array::(new_cap).unwrap()); (new_cap, ptr) }; - // If allocate or reallocate fail, we'll get `null` back - if ptr.is_null() { oom(); } + // If allocate or reallocate fail, oom + if ptr.is_err() { + handle_alloc_error(Layout::from_size_align_unchecked( + new_cap * elem_size, + mem::align_of::(), + )) + } + + 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; } } @@ -205,4 +201,3 @@ fn grow(&mut self) { Nothing particularly tricky here. Just computing sizes and alignments and doing some careful multiplication checks. - diff --git a/src/vec-dealloc.md b/src/vec-dealloc.md index 2f1b0b5..34ddc2b 100644 --- a/src/vec-dealloc.md +++ b/src/vec-dealloc.md @@ -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 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. @@ -17,11 +17,10 @@ impl Drop for Vec { if self.cap != 0 { while let Some(_) = self.pop() { } - let align = mem::align_of::(); - let elem_size = mem::size_of::(); - let num_bytes = elem_size * self.cap; unsafe { - heap::deallocate(self.ptr.as_ptr() as *mut _, num_bytes, align); + let c: NonNull = self.ptr.into(); + Global.deallocate(c.cast(), + Layout::array::(self.cap).unwrap()); } } } diff --git a/src/vec-drain.md b/src/vec-drain.md index 3441c90..a2fd0eb 100644 --- a/src/vec-drain.md +++ b/src/vec-drain.md @@ -26,7 +26,7 @@ impl<'a, T> Iterator for Drain<'a, T> { -- 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. -```rust +```rust,ignore struct RawValIter { start: *const T, end: *const T, @@ -75,7 +75,7 @@ impl DoubleEndedIterator for IntoIter { impl Drop for IntoIter { 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> { fn drop(&mut self) { - for _ in &mut self.iter {} + for _ in &mut *self {} } } diff --git a/src/vec-final.md b/src/vec-final.md index 1bb2015..e5f3529 100644 --- a/src/vec-final.md +++ b/src/vec-final.md @@ -174,7 +174,7 @@ impl Vec { impl Drop for Vec { fn drop(&mut self) { while let Some(_) = self.pop() {} - // allocation is handled by RawVec + // deallocation is handled by RawVec } } @@ -214,7 +214,7 @@ impl RawValIter { slice.as_ptr() } else { 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> { fn drop(&mut self) { // pre-drain the iter - for _ in &mut self.iter {} + for _ in &mut *self {} } } - +# # fn main() { # tests::create_push_pop(); # tests::iter_test(); @@ -318,7 +318,7 @@ impl<'a, T> Drop for Drain<'a, T> { # tests::test_zst(); # println!("All tests finished OK"); # } - +# # mod tests { # use super::*; # pub fn create_push_pop() { diff --git a/src/vec-insert-remove.md b/src/vec-insert-remove.md index 2c14bc4..c02a16d 100644 --- a/src/vec-insert-remove.md +++ b/src/vec-insert-remove.md @@ -22,11 +22,11 @@ pub fn insert(&mut self, index: usize, elem: T) { unsafe { if index < self.len { // ptr::copy(src, dest, len): "copy from source to dest len elems" - ptr::copy(self.ptr.offset(index as isize), - self.ptr.offset(index as isize + 1), + ptr::copy(self.ptr.as_ptr().offset(index as isize), + self.ptr.as_ptr().offset(index as isize + 1), 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; } } @@ -41,9 +41,9 @@ pub fn remove(&mut self, index: usize) -> T { assert!(index < self.len, "index out of bounds"); unsafe { self.len -= 1; - let result = ptr::read(self.ptr.offset(index as isize)); - ptr::copy(self.ptr.offset(index as isize + 1), - self.ptr.offset(index as isize), + let result = ptr::read(self.ptr.as_ptr().offset(index as isize)); + ptr::copy(self.ptr.as_ptr().offset(index as isize + 1), + self.ptr.as_ptr().offset(index as isize), self.len - index); result } diff --git a/src/vec-into-iter.md b/src/vec-into-iter.md index df36757..02326f6 100644 --- a/src/vec-into-iter.md +++ b/src/vec-into-iter.md @@ -43,7 +43,7 @@ dropped. So we're going to use the following struct: ```rust,ignore -struct IntoIter { +pub struct IntoIter { buf: Unique, cap: usize, start: *const T, @@ -55,7 +55,7 @@ And this is what we end up with for initialization: ```rust,ignore impl Vec { - fn into_iter(self) -> IntoIter { + pub fn into_iter(self) -> IntoIter { // Can't destructure Vec since it's Drop let ptr = self.ptr; let cap = self.cap; @@ -68,13 +68,13 @@ impl Vec { IntoIter { buf: ptr, cap: cap, - start: *ptr, + start: ptr.as_ptr(), end: if cap == 0 { // can't offset off this pointer, it's not allocated! - *ptr + ptr.as_ptr() } else { - ptr.offset(len as isize) - } + ptr.as_ptr().offset(len as isize) + }, } } } @@ -135,11 +135,10 @@ impl Drop for IntoIter { // drop any remaining elements for _ in &mut *self {} - let align = mem::align_of::(); - let elem_size = mem::size_of::(); - let num_bytes = elem_size * self.cap; unsafe { - heap::deallocate(self.buf.as_ptr() as *mut _, num_bytes, align); + let c: NonNull = self.buf.into(); + Global.deallocate(c.cast(), + Layout::array::(self.cap).unwrap()); } } } diff --git a/src/vec-layout.md b/src/vec-layout.md index c9962e2..26c979f 100644 --- a/src/vec-layout.md +++ b/src/vec-layout.md @@ -6,13 +6,12 @@ elements that have been initialized. Naively, this means we just want this design: -```rust +```rust,ignore pub struct Vec { ptr: *mut T, cap: usize, len: usize, } -# fn main() {} ``` 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 one in stable Rust: -```rust +```rust,ignore use std::marker::PhantomData; use std::ops::Deref; use std::mem; @@ -61,8 +60,6 @@ impl Unique { self.ptr as *mut T } } - -# fn main() {} ``` 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: -```rust -#![feature(ptr_internals)] - -use std::ptr::{Unique, self}; - +```rust,ignore pub struct Vec { ptr: Unique, cap: usize, len: usize, } - -# fn main() {} ``` If you don't care about the null-pointer optimization, then you can use the diff --git a/src/vec-push-pop.md b/src/vec-push-pop.md index d31a74c..6f39a05 100644 --- a/src/vec-push-pop.md +++ b/src/vec-push-pop.md @@ -22,7 +22,7 @@ pub fn push(&mut self, elem: T) { if self.len == self.cap { self.grow(); } 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. @@ -48,7 +48,7 @@ pub fn pop(&mut self) -> Option { } else { self.len -= 1; unsafe { - Some(ptr::read(self.ptr.offset(self.len as isize))) + Some(ptr::read(self.ptr.as_ptr().offset(self.len as isize))) } } } diff --git a/src/vec-raw.md b/src/vec-raw.md index f651d3f..7303a54 100644 --- a/src/vec-raw.md +++ b/src/vec-raw.md @@ -1,4 +1,3 @@ - # RawVec We've actually reached an interesting situation here: we've duplicated the logic @@ -17,46 +16,50 @@ struct RawVec { impl RawVec { fn new() -> Self { - assert!(mem::size_of::() != 0, "TODO: implement ZST support"); + assert!(mem::size_of::() != 0, "We're not ready to handle ZSTs"); RawVec { ptr: Unique::dangling(), cap: 0 } } // unchanged from Vec fn grow(&mut self) { unsafe { - let align = mem::align_of::(); let elem_size = mem::size_of::(); let (new_cap, ptr) = if self.cap == 0 { - let ptr = heap::allocate(elem_size, align); + let ptr = Global.allocate(Layout::array::(1).unwrap()); (1, ptr) } else { let new_cap = 2 * self.cap; - let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _, - self.cap * elem_size, - new_cap * elem_size, - align); + let c: NonNull = self.ptr.into(); + let ptr = Global.grow(c.cast(), + Layout::array::(self.cap).unwrap(), + Layout::array::(new_cap).unwrap()); (new_cap, ptr) }; - // If allocate or reallocate fail, we'll get `null` back - if ptr.is_null() { oom() } + // If allocate or reallocate fail, oom + if ptr.is_err() { + handle_alloc_error(Layout::from_size_align_unchecked( + new_cap * elem_size, + mem::align_of::(), + )) + } + + 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; } } } - impl Drop for RawVec { fn drop(&mut self) { if self.cap != 0 { - let align = mem::align_of::(); - let elem_size = mem::size_of::(); - let num_bytes = elem_size * self.cap; unsafe { - heap::deallocate(self.ptr.as_mut() as *mut _, num_bytes, align); + let c: NonNull = self.ptr.into(); + Global.deallocate(c.cast(), + Layout::array::(self.cap).unwrap()); } } } @@ -81,7 +84,7 @@ impl Vec { } // push/pop/insert/remove largely unchanged: - // * `self.ptr -> self.ptr()` + // * `self.ptr.as_ptr() -> self.ptr()` // * `self.cap -> self.cap()` // * `self.grow -> self.buf.grow()` } @@ -97,7 +100,7 @@ impl Drop for Vec { And finally we can really simplify IntoIter: ```rust,ignore -struct IntoIter { +pub struct IntoIter { _buf: RawVec, // we don't actually care about this. Just need it to live. start: *const T, end: *const T, @@ -123,8 +126,8 @@ impl Vec { mem::forget(self); IntoIter { - start: *buf.ptr, - end: buf.ptr.offset(len as isize), + start: buf.ptr.as_ptr(), + end: buf.ptr.as_ptr().offset(len as isize), _buf: buf, } } diff --git a/src/vec-zsts.md b/src/vec-zsts.md index c404524..418f557 100644 --- a/src/vec-zsts.md +++ b/src/vec-zsts.md @@ -11,7 +11,7 @@ zero-sized types. We need to be careful of two things: C-style pointer iterator. 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. Due to our current architecture, all this means is writing 3 guards, one in each -method of RawVec. +method of `RawVec`. ```rust,ignore impl RawVec { @@ -50,24 +50,29 @@ impl RawVec { // 0, getting to here necessarily means the Vec is overfull. assert!(elem_size != 0, "capacity overflow"); - let align = mem::align_of::(); - let (new_cap, ptr) = if self.cap == 0 { - let ptr = heap::allocate(elem_size, align); + let ptr = Global.allocate(Layout::array::(1).unwrap()); (1, ptr) } else { let new_cap = 2 * self.cap; - let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _, - self.cap * elem_size, - new_cap * elem_size, - align); + let c: NonNull = self.ptr.into(); + let ptr = Global.grow(c.cast(), + Layout::array::(self.cap).unwrap(), + Layout::array::(new_cap).unwrap()); (new_cap, ptr) }; - // If allocate or reallocate fail, we'll get `null` back - if ptr.is_null() { oom() } + // If allocate or reallocate fail, oom + if ptr.is_err() { + handle_alloc_error(Layout::from_size_align_unchecked( + new_cap * elem_size, + mem::align_of::(), + )) + } + + 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; } } @@ -79,11 +84,10 @@ impl Drop for RawVec { // don't free zero-sized allocations, as they were never allocated. if self.cap != 0 && elem_size != 0 { - let align = mem::align_of::(); - - let num_bytes = elem_size * self.cap; unsafe { - heap::deallocate(self.ptr.as_ptr() as *mut _, num_bytes, align); + let c: NonNull = self.ptr.into(); + Global.deallocate(c.cast(), + Layout::array::(self.cap).unwrap()); } } } @@ -114,7 +118,7 @@ impl RawValIter { slice.as_ptr() } else { slice.as_ptr().offset(slice.len() as isize) - } + }, } } }