diff --git a/checked-uninit.md b/checked-uninit.md index 667afe0..65bffd7 100644 --- a/checked-uninit.md +++ b/checked-uninit.md @@ -4,7 +4,7 @@ Like C, all stack variables in Rust are uninitialized until a value is explicitly assigned to them. Unlike C, Rust statically prevents you from ever reading them until you do: -```rust +```rust,ignore fn main() { let x: i32; println!("{}", x); @@ -39,7 +39,7 @@ fn main() { but this doesn't: -```rust +```rust,ignore fn main() { let x: i32; if true { diff --git a/coercions.md b/coercions.md index fad9b09..df0fdfa 100644 --- a/coercions.md +++ b/coercions.md @@ -51,7 +51,7 @@ receivers, see below). If there is an impl for some type `U` and `T` coerces to following will not type check, even though it is OK to coerce `t` to `&T` and there is an impl for `&T`: -```rust +```rust,ignore trait Trait {} fn foo(t: X) {} diff --git a/destructors.md b/destructors.md index cf6378c..e146ae4 100644 --- a/destructors.md +++ b/destructors.md @@ -3,7 +3,7 @@ What the language *does* provide is full-blown automatic destructors through the `Drop` trait, which provides the following method: -```rust +```rust,ignore fn drop(&mut self); ``` @@ -23,13 +23,22 @@ this is totally fine. For instance, a custom implementation of `Box` might write `Drop` like this: ```rust -struct Box{ ptr: *mut T } +#![feature(heap_api, core_intrinsics, unique)] + +use std::rt::heap; +use std::ptr::Unique; +use std::intrinsics::drop_in_place; +use std::mem; + +struct Box{ ptr: Unique } impl Drop for Box { fn drop(&mut self) { unsafe { - (*self.ptr).drop(); - heap::deallocate(self.ptr); + drop_in_place(*self.ptr); + heap::deallocate((*self.ptr) as *mut u8, + mem::size_of::(), + mem::align_of::()); } } } @@ -42,25 +51,36 @@ after-free the `ptr` because the Box is immediately marked as uninitialized. However this wouldn't work: ```rust -struct Box{ ptr: *mut T } +#![feature(heap_api, core_intrinsics, unique)] + +use std::rt::heap; +use std::ptr::Unique; +use std::intrinsics::drop_in_place; +use std::mem; + +struct Box{ ptr: Unique } impl Drop for Box { fn drop(&mut self) { unsafe { - (*self.ptr).drop(); - heap::deallocate(self.ptr); + drop_in_place(*self.ptr); + heap::deallocate((*self.ptr) as *mut u8, + mem::size_of::(), + mem::align_of::()); } } } -struct SuperBox { box: Box } +struct SuperBox { my_box: Box } impl Drop for SuperBox { fn drop(&mut self) { unsafe { // Hyper-optimized: deallocate the box's contents for it // without `drop`ing the contents - heap::deallocate(self.box.ptr); + heap::deallocate((*self.my_box.ptr) as *mut u8, + mem::size_of::(), + mem::align_of::()); } } } @@ -106,18 +126,27 @@ The classic safe solution to overriding recursive drop and allowing moving out of Self during `drop` is to use an Option: ```rust -struct Box{ ptr: *mut T } +#![feature(heap_api, core_intrinsics, unique)] + +use std::rt::heap; +use std::ptr::Unique; +use std::intrinsics::drop_in_place; +use std::mem; + +struct Box{ ptr: Unique } impl Drop for Box { fn drop(&mut self) { unsafe { - (*self.ptr).drop(); - heap::deallocate(self.ptr); + drop_in_place(*self.ptr); + heap::deallocate((*self.ptr) as *mut u8, + mem::size_of::(), + mem::align_of::()); } } } -struct SuperBox { box: Option> } +struct SuperBox { my_box: Option> } impl Drop for SuperBox { fn drop(&mut self) { @@ -125,7 +154,11 @@ impl Drop for SuperBox { // Hyper-optimized: deallocate the box's contents for it // without `drop`ing the contents. Need to set the `box` // field as `None` to prevent Rust from trying to Drop it. - heap::deallocate(self.box.take().unwrap().ptr); + let my_box = self.my_box.take().unwrap(); + heap::deallocate((*my_box.ptr) as *mut u8, + mem::size_of::(), + mem::align_of::()); + mem::forget(my_box); } } } diff --git a/drop-flags.md b/drop-flags.md index 68f7ffc..e8c331c 100644 --- a/drop-flags.md +++ b/drop-flags.md @@ -31,6 +31,7 @@ And even branched code where all branches have the same behaviour with respect to initialization: ```rust +# let condition = true; let mut x = Box::new(0); // x was uninit; just overwrite. if condition { drop(x) // x gets moved out; make x uninit. @@ -45,6 +46,7 @@ x = Box::new(0); // x was uninit; just overwrite. However code like this *requires* runtime information to correctly Drop: ```rust +# let condition = true; let x; if condition { x = Box::new(0); // x was uninit; just overwrite. @@ -56,6 +58,7 @@ if condition { Of course, in this case it's trivial to retrieve static drop semantics: ```rust +# let condition = true; if condition { let x = Box::new(0); println!("{}", x); diff --git a/exception-safety.md b/exception-safety.md index ca33109..9a31934 100644 --- a/exception-safety.md +++ b/exception-safety.md @@ -156,7 +156,7 @@ way to do this is to store the algorithm's state in a separate struct with a destructor for the "finally" logic. Whether we panic or not, that destructor will run and clean up after us. -```rust +```rust,ignore struct Hole<'a, T: 'a> { data: &'a mut [T], /// `elt` is always `Some` from new until drop. diff --git a/hrtb.md b/hrtb.md index c3f2502..640742f 100644 --- a/hrtb.md +++ b/hrtb.md @@ -28,7 +28,7 @@ fn main() { If we try to naively desugar this code in the same way that we did in the lifetimes section, we run into some trouble: -```rust +```rust,ignore struct Closure { data: (u8, u16), func: F, @@ -60,7 +60,7 @@ we enter the body of `call`! Also, that isn't some fixed lifetime; call works wi This job requires The Magic of Higher-Rank Trait Bounds. The way we desugar this is as follows: -```rust +```rust,ignore where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8, ``` @@ -69,4 +69,4 @@ where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8, `for<'a>` can be read as "for all choices of `'a`", and basically produces an *inifinite list* of trait bounds that F must satisfy. Intense. There aren't many places outside of the Fn traits where we encounter HRTBs, and even for those we -have a nice magic sugar for the common cases. \ No newline at end of file +have a nice magic sugar for the common cases. diff --git a/leaking.md b/leaking.md index 1e1e95a..bb6f7bb 100644 --- a/leaking.md +++ b/leaking.md @@ -68,7 +68,7 @@ unwinding-safe! Easy! Now consider the following: -``` +```rust,ignore let mut vec = vec![Box::new(0); 4]; { @@ -118,7 +118,7 @@ Nope. Let's consider a simplified implementation of Rc: -```rust +```rust,ignore struct Rc { ptr: *mut RcBox, } @@ -183,7 +183,7 @@ in memory. The thread::scoped API intends to allow threads to be spawned that reference data on the stack without any synchronization over that data. Usage looked like: -```rust +```rust,ignore let mut data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; { let guards = vec![]; @@ -211,7 +211,7 @@ let mut data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; In principle, this totally works! Rust's ownership system perfectly ensures it! ...except it relies on a destructor being called to be safe. -``` +```rust,ignore let mut data = Box::new(0); { let guard = thread::scoped(|| { diff --git a/lifetime-elision.md b/lifetime-elision.md index eac2433..41014f4 100644 --- a/lifetime-elision.md +++ b/lifetime-elision.md @@ -5,7 +5,7 @@ In order to make common patterns more ergonomic, Rust allows lifetimes to be A *lifetime position* is anywhere you can write a lifetime in a type: -```rust +```rust,ignore &'a T &'a mut T T<'a> @@ -38,7 +38,7 @@ Elision rules are as follows: Examples: -```rust +```rust,ignore fn print(s: &str); // elided fn print<'a>(s: &'a str); // expanded @@ -61,4 +61,4 @@ fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command // exp fn new(buf: &mut [u8]) -> BufWriter; // elided fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a> // expanded -``` \ No newline at end of file +``` diff --git a/lifetime-misc.md b/lifetime-misc.md index faf7f9a..bd44bb6 100644 --- a/lifetime-misc.md +++ b/lifetime-misc.md @@ -10,8 +10,8 @@ types or lifetimes are logically associated with a struct, but not actually part of a field. This most commonly occurs with lifetimes. For instance, the `Iter` for `&'a [T]` is (approximately) defined as follows: -```rust -pub struct Iter<'a, T: 'a> { +```rust,ignore +struct Iter<'a, T: 'a> { ptr: *const T, end: *const T, } @@ -33,7 +33,9 @@ Iter logically contains `&'a T`, so this is exactly what we tell the PhantomData to simulate: ``` -pub struct Iter<'a, T: 'a> { +use std::marker; + +struct Iter<'a, T: 'a> { ptr: *const T, end: *const T, _marker: marker::PhantomData<&'a T>, @@ -68,6 +70,8 @@ tell dropck that we *do* own values of type T, and may call destructors of that type, we must add extra PhantomData: ``` +use std::marker; + struct Vec { data: *const T, // *const for covariance! len: usize, @@ -115,7 +119,7 @@ println!("{} {} {} {}", a, b, c, c2); However borrowck doesn't understand arrays or slices in any way, so this doesn't work: -```rust +```rust,ignore let x = [1, 2, 3]; let a = &mut x[0]; let b = &mut x[1]; @@ -144,7 +148,7 @@ left of the index, and one for everything to the right. Intuitively we know this is safe because the slices don't alias. However the implementation requires some unsafety: -```rust +```rust,ignore fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) { unsafe { let self2: &mut [T] = mem::transmute_copy(&self); @@ -189,8 +193,8 @@ Whether it's raw pointers, or safely composing on top of *another* IterMut. For instance, VecDeque's IterMut: -```rust -pub struct IterMut<'a, T:'a> { +```rust,ignore +struct IterMut<'a, T:'a> { // The whole backing array. Some of these indices are initialized! ring: &'a mut [T], tail: usize, diff --git a/lifetimes.md b/lifetimes.md index a06363a..7282e4d 100644 --- a/lifetimes.md +++ b/lifetimes.md @@ -38,7 +38,7 @@ let z = &y; The borrow checker always tries to minimize the extent of a lifetime, so it will likely desugar to the following: -```rust +```rust,ignore // NOTE: `'a: {` and `&'b x` is not valid syntax! 'a: { let x: i32 = 0; @@ -69,8 +69,8 @@ z = y; The borrow checker always tries to minimize the extent of a lifetime, so it will likely desugar to something like the following: -```rust -// NOTE: `'a: {` and `&'b x` is not valid syntax! +```rust,ignore +// NOTE: `'a: {` and `foo = &'b x` is not valid syntax! 'a: { let x: i32 = 0; 'b: { @@ -174,14 +174,14 @@ our implementation *just a bit*.) How about the other example: -```rust +```rust,ignore let mut data = vec![1, 2, 3]; let x = &data[0]; data.push(4); println!("{}", x); ``` -```rust +```rust,ignore 'a: { let mut data: Vec = vec![1, 2, 3]; 'b: { @@ -219,4 +219,4 @@ semantics we're actually interested in preserving. For the most part, *that's totally ok*, because it keeps us from spending all day explaining our program to the compiler. However it does mean that several programs that are *totally* correct with respect to Rust's *true* semantics are rejected because lifetimes -are too dumb. \ No newline at end of file +are too dumb. diff --git a/ownership.md b/ownership.md index 9c4f92a..200337a 100644 --- a/ownership.md +++ b/ownership.md @@ -16,7 +16,7 @@ issue...). This is a pervasive problem that C and C++ need to deal with. Consider this simple mistake that all of us who have used a non-GC'd language have made at one point: -```rust +```rust,ignore fn as_str(data: &u32) -> &str { // compute the string let s = format!("{}", data); @@ -45,7 +45,7 @@ verifying that references don't escape the scope of their referent. That's because ensuring pointers are always valid is much more complicated than this. For instance in this code, -```rust +```rust,ignore let mut data = vec![1, 2, 3]; // get an internal reference let x = &data[0]; diff --git a/repr-rust.md b/repr-rust.md index a1f5f29..b3a5a12 100644 --- a/repr-rust.md +++ b/repr-rust.md @@ -67,7 +67,7 @@ fields in the order specified, we expect it to *pad* the values in the struct to their *alignment* requirements. So if Rust didn't reorder fields, we would expect Rust to produce the following: -```rust +```rust,ignore struct Foo { count: u16, data1: u16, diff --git a/send-and-sync.md b/send-and-sync.md index 6045d3d..5b00709 100644 --- a/send-and-sync.md +++ b/send-and-sync.md @@ -56,6 +56,8 @@ In the *incredibly rare* case that a type is *inappropriately* automatically derived to be Send or Sync, then one can also *unimplement* Send and Sync: ```rust +#![feature(optin_builtin_traits)] + struct SpecialThreadToken(u8); impl !Send for SpecialThreadToken {} diff --git a/subtyping.md b/subtyping.md index 24f974c..e43c365 100644 --- a/subtyping.md +++ b/subtyping.md @@ -104,7 +104,7 @@ However what should happen when passing *by-value* is less obvious. It turns out that, yes, you can use subtyping when passing by-value. That is, this works: ```rust -fn get_box<'a>(&'a u8) -> Box<&'a str> { +fn get_box<'a>(str: &'a u8) -> Box<&'a str> { // string literals are `&'static str`s Box::new("hello") } @@ -123,7 +123,7 @@ must be invariant to avoid lifetime smuggling. `Fn(T) -> U` should be invariant over T, consider the following function signature: -```rust +```rust,ignore // 'a is derived from some parent scope fn foo(&'a str) -> usize; ``` @@ -131,7 +131,7 @@ fn foo(&'a str) -> usize; This signature claims that it can handle any `&str` that lives *at least* as long as `'a`. Now if this signature was variant with respect to `&str`, that would mean -```rust +```rust,ignore fn foo(&'static str) -> usize; ``` @@ -142,7 +142,7 @@ and nothing else. Therefore functions are not variant over their arguments. To see why `Fn(T) -> U` should be *variant* over U, consider the following function signature: -```rust +```rust,ignore // 'a is derived from some parent scope fn foo(usize) -> &'a str; ``` @@ -150,7 +150,7 @@ fn foo(usize) -> &'a str; This signature claims that it will return something that outlives `'a`. It is therefore completely reasonable to provide -```rust +```rust,ignore fn foo(usize) -> &'static str; ``` @@ -171,15 +171,17 @@ in multiple fields. * Otherwise, Foo is invariant over A ```rust -struct Foo<'a, 'b, A, B, C, D, E, F, G, H> { +use std::cell::Cell; + +struct Foo<'a, 'b, A: 'a, B: 'b, C, D, E, F, G, H> { a: &'a A, // variant over 'a and A b: &'b mut B, // invariant over 'b and B c: *const C, // variant over C d: *mut D, // invariant over D e: Vec, // variant over E f: Cell, // invariant over F - g: G // variant over G - h1: H // would also be variant over H except... - h2: Cell // invariant over H, because invariance wins + g: G, // variant over G + h1: H, // would also be variant over H except... + h2: Cell, // invariant over H, because invariance wins } -``` \ No newline at end of file +``` diff --git a/unbounded-lifetimes.md b/unbounded-lifetimes.md index 24caeeb..b540ab4 100644 --- a/unbounded-lifetimes.md +++ b/unbounded-lifetimes.md @@ -17,7 +17,7 @@ boundaries. Given a function, any output lifetimes that don't derive from inputs are unbounded. For instance: -```rust +```rust,ignore fn get_str<'a>() -> &'a str; ``` diff --git a/unchecked-uninit.md b/unchecked-uninit.md index f5c0fb4..9ab97b9 100644 --- a/unchecked-uninit.md +++ b/unchecked-uninit.md @@ -46,27 +46,26 @@ locations of memory can break things are basically uncountable! Putting this all together, we get the following: ```rust -fn main() { - use std::mem; - - // size of the array is hard-coded but easy to change. This means we can't - // use [a, b, c] syntax to initialize the array, though! - const SIZE = 10; - - let x: [Box; SIZE]; - - unsafe { - // convince Rust that x is Totally Initialized - x = mem::uninitialized(); - for i in 0..SIZE { - // very carefully overwrite each index without reading it - // NOTE: exception safety is not a concern; Box can't panic - ptr::write(&mut x[i], Box::new(i)); - } +use std::mem; +use std::ptr; + +// size of the array is hard-coded but easy to change. This means we can't +// use [a, b, c] syntax to initialize the array, though! +const SIZE: usize = 10; + +let mut x: [Box; SIZE]; + +unsafe { + // convince Rust that x is Totally Initialized + x = mem::uninitialized(); + for i in 0..SIZE { + // very carefully overwrite each index without reading it + // NOTE: exception safety is not a concern; Box can't panic + ptr::write(&mut x[i], Box::new(i as u32)); } - - println!("{}", x); } + +println!("{:?}", x); ``` It's worth noting that you don't need to worry about ptr::write-style @@ -83,4 +82,4 @@ before it ends, if has a destructor. And that's about it for working with uninitialized memory! Basically nothing anywhere expects to be handed uninitialized memory, so if you're going to pass -it around at all, be sure to be *really* careful. \ No newline at end of file +it around at all, be sure to be *really* careful. diff --git a/vec-alloc.md b/vec-alloc.md index a51f23c..e9c9f68 100644 --- a/vec-alloc.md +++ b/vec-alloc.md @@ -2,7 +2,7 @@ So: -```rust +```rust,ignore #![feature(heap_api)] use std::rt::heap::EMPTY; @@ -69,7 +69,7 @@ Anything else will use up too much space. However since this is a tutorial, we're not going to be particularly optimal here, and just unconditionally check, rather than use clever platform-specific `cfg`s. -```rust +```rust,ignore fn grow(&mut self) { // this is all pretty delicate, so let's say it's all unsafe unsafe { diff --git a/vec-dealloc.md b/vec-dealloc.md index a83d24d..2ae2477 100644 --- a/vec-dealloc.md +++ b/vec-dealloc.md @@ -11,13 +11,13 @@ We must not call `heap::deallocate` when `self.cap == 0`, as in this case we hav actually allocated any memory. -```rust +```rust,ignore impl Drop for Vec { fn drop(&mut self) { if self.cap != 0 { while let Some(_) = self.pop() { } - let align = mem::min_align_of::(); + let align = mem::align_of::(); let elem_size = mem::size_of::(); let num_bytes = elem_size * self.cap; unsafe { diff --git a/vec-deref.md b/vec-deref.md index b07d784..826d763 100644 --- a/vec-deref.md +++ b/vec-deref.md @@ -9,7 +9,7 @@ conditions. All we need is `slice::from_raw_parts`. -```rust +```rust,ignore use std::ops::Deref; impl Deref for Vec { @@ -24,7 +24,7 @@ impl Deref for Vec { And let's do DerefMut too: -```rust +```rust,ignore use std::ops::DerefMut; impl DerefMut for Vec { diff --git a/vec-drain.md b/vec-drain.md index 0a53e8b..8dd085d 100644 --- a/vec-drain.md +++ b/vec-drain.md @@ -51,7 +51,7 @@ impl RawValIter { And IntoIter becomes the following: -``` +```rust,ignore pub struct IntoIter { _buf: RawVec, // we don't actually care about this. Just need it to live. iter: RawValIter, @@ -96,7 +96,7 @@ We also take a slice to simplify Drain initialization. Alright, now Drain is really easy: -```rust +```rust,ignore use std::marker::PhantomData; pub struct Drain<'a, T: 'a> { @@ -174,7 +174,7 @@ overflow for zero-sized types. Due to our current architecture, all this means is writing 3 guards, one in each method of RawVec. -```rust +```rust,ignore impl RawVec { fn new() -> Self { unsafe { @@ -194,7 +194,7 @@ impl RawVec { // 0, getting to here necessarily means the Vec is overfull. assert!(elem_size != 0, "capacity overflow"); - let align = mem::min_align_of::(); + let align = mem::align_of::(); let (new_cap, ptr) = if self.cap == 0 { let ptr = heap::allocate(elem_size, align); @@ -223,7 +223,7 @@ 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::min_align_of::(); + let align = mem::align_of::(); let num_bytes = elem_size * self.cap; unsafe { @@ -247,7 +247,7 @@ initialize `start` and `end` as the same value, and our iterators will yield nothing. The current solution to this is to cast the pointers to integers, increment, and then cast them back: -``` +```rust,ignore impl RawValIter { unsafe fn new(slice: &[T]) -> Self { RawValIter { @@ -270,7 +270,7 @@ Also, our size_hint computation code will divide by 0 for ZSTs. Since we'll basically be treating the two pointers as if they point to bytes, we'll just map size 0 to divide by 1. -``` +```rust,ignore impl Iterator for RawValIter { type Item = T; fn next(&mut self) -> Option { @@ -315,4 +315,4 @@ impl DoubleEndedIterator for RawValIter { } ``` -And that's it. Iteration works! \ No newline at end of file +And that's it. Iteration works! diff --git a/vec-final.md b/vec-final.md index 96fcf6d..847957e 100644 --- a/vec-final.md +++ b/vec-final.md @@ -38,7 +38,7 @@ impl RawVec { // 0, getting to here necessarily means the Vec is overfull. assert!(elem_size != 0, "capacity overflow"); - let align = mem::min_align_of::(); + let align = mem::align_of::(); let (new_cap, ptr) = if self.cap == 0 { let ptr = heap::allocate(elem_size, align); @@ -65,7 +65,7 @@ impl Drop for RawVec { fn drop(&mut self) { let elem_size = mem::size_of::(); if self.cap != 0 && elem_size != 0 { - let align = mem::min_align_of::(); + let align = mem::align_of::(); let num_bytes = elem_size * self.cap; unsafe { @@ -306,4 +306,6 @@ impl<'a, T> Drop for Drain<'a, T> { fn oom() { ::std::process::exit(-9999); } -``` \ No newline at end of file + +# fn main() {} +``` diff --git a/vec-insert-remove.md b/vec-insert-remove.md index 42d114c..f21ed22 100644 --- a/vec-insert-remove.md +++ b/vec-insert-remove.md @@ -11,7 +11,7 @@ here). If we insert at index `i`, we want to shift the `[i .. len]` to `[i+1 .. len+1]` using the *old* len. -```rust +```rust,ignore pub fn insert(&mut self, index: usize, elem: T) { // Note: `<=` because it's valid to insert after everything // which would be equivalent to push. @@ -34,7 +34,7 @@ pub fn insert(&mut self, index: usize, elem: T) { Remove behaves in the opposite manner. We need to shift all the elements from `[i+1 .. len + 1]` to `[i .. len]` using the *new* len. -```rust +```rust,ignore pub fn remove(&mut self, index: usize) -> T { // Note: `<` because it's *not* valid to remove after everything assert!(index < self.len, "index out of bounds"); @@ -47,4 +47,4 @@ pub fn remove(&mut self, index: usize) -> T { result } } -``` \ No newline at end of file +``` diff --git a/vec-into-iter.md b/vec-into-iter.md index b7e7d2b..d21cf94 100644 --- a/vec-into-iter.md +++ b/vec-into-iter.md @@ -37,7 +37,7 @@ indistinguishable from the case where there are no more elements to yield. So we're going to use the following struct: -```rust +```rust,ignore struct IntoIter { buf: Unique, cap: usize, @@ -63,7 +63,7 @@ cap or len being 0 to not do the offset. So this is what we end up with for initialization: -```rust +```rust,ignore impl Vec { fn into_iter(self) -> IntoIter { // Can't destructure Vec since it's Drop @@ -93,7 +93,7 @@ impl Vec { Here's iterating forward: -```rust +```rust,ignore impl Iterator for IntoIter { type Item = T; fn next(&mut self) -> Option { @@ -118,7 +118,7 @@ impl Iterator for IntoIter { And here's iterating backwards. -```rust +```rust,ignore impl DoubleEndedIterator for IntoIter { fn next_back(&mut self) -> Option { if self.start == self.end { @@ -138,14 +138,14 @@ to free it. However it *also* wants to implement Drop to drop any elements it contains that weren't yielded. -```rust +```rust,ignore impl Drop for IntoIter { fn drop(&mut self) { if self.cap != 0 { // drop any remaining elements for _ in &mut *self {} - let align = mem::min_align_of::(); + let align = mem::align_of::(); let elem_size = mem::size_of::(); let num_bytes = elem_size * self.cap; unsafe { @@ -164,7 +164,7 @@ compression. We're going to abstract out the `(ptr, cap)` pair and give them the logic for allocating, growing, and freeing: -```rust +```rust,ignore struct RawVec { ptr: Unique, @@ -182,7 +182,7 @@ impl RawVec { // unchanged from Vec fn grow(&mut self) { unsafe { - let align = mem::min_align_of::(); + let align = mem::align_of::(); let elem_size = mem::size_of::(); let (new_cap, ptr) = if self.cap == 0 { @@ -210,7 +210,7 @@ impl RawVec { impl Drop for RawVec { fn drop(&mut self) { if self.cap != 0 { - let align = mem::min_align_of::(); + let align = mem::align_of::(); let elem_size = mem::size_of::(); let num_bytes = elem_size * self.cap; unsafe { @@ -223,7 +223,7 @@ impl Drop for RawVec { And change vec as follows: -```rust +```rust,ignore pub struct Vec { buf: RawVec, len: usize, @@ -254,14 +254,14 @@ impl Drop for Vec { And finally we can really simplify IntoIter: -```rust +```rust,ignore struct IntoIter { _buf: RawVec, // we don't actually care about this. Just need it to live. start: *const T, end: *const T, } -// next and next_back litterally unchanged since they never referred to the buf +// next and next_back literally unchanged since they never referred to the buf impl Drop for IntoIter { fn drop(&mut self) { @@ -290,4 +290,4 @@ impl Vec { } ``` -Much better. \ No newline at end of file +Much better. diff --git a/vec-layout.md b/vec-layout.md index 0f85e4d..128ad15 100644 --- a/vec-layout.md +++ b/vec-layout.md @@ -4,11 +4,13 @@ First off, we need to come up with the struct layout. Naively we want this design: ```rust -struct Vec { +pub struct Vec { ptr: *mut T, cap: usize, len: usize, } + +# fn main() {} ``` And indeed this would compile. Unfortunately, it would be incorrect. The compiler @@ -32,6 +34,8 @@ pub struct Vec { cap: usize, len: usize, } + +# fn main() {} ``` As a recap, Unique is a wrapper around a raw pointer that declares that: diff --git a/vec-push-pop.md b/vec-push-pop.md index d1584a2..2ef15e3 100644 --- a/vec-push-pop.md +++ b/vec-push-pop.md @@ -17,7 +17,7 @@ target address with the bits of the value we provide. No evaluation involved. For `push`, if the old len (before push was called) is 0, then we want to write to the 0th index. So we should offset by the old len. -```rust +```rust,ignore pub fn push(&mut self, elem: T) { if self.len == self.cap { self.grow(); } @@ -41,7 +41,7 @@ of T there. For `pop`, if the old len is 1, we want to read out of the 0th index. So we should offset by the *new* len. -```rust +```rust,ignore pub fn pop(&mut self) -> Option { if self.len == 0 { None @@ -52,4 +52,4 @@ pub fn pop(&mut self) -> Option { } } } -``` \ No newline at end of file +``` diff --git a/working-with-unsafe.md b/working-with-unsafe.md index 69d0b31..0aeb2c0 100644 --- a/working-with-unsafe.md +++ b/working-with-unsafe.md @@ -5,7 +5,7 @@ binary manner. Unfortunately, reality is significantly more complicated than tha For instance, consider the following toy function: ```rust -pub fn index(idx: usize, arr: &[u8]) -> Option { +fn index(idx: usize, arr: &[u8]) -> Option { if idx < arr.len() { unsafe { Some(*arr.get_unchecked(idx)) @@ -22,7 +22,7 @@ function, the scope of the unsafe block is questionable. Consider changing the `<` to a `<=`: ```rust -pub fn index(idx: usize, arr: &[u8]) -> Option { +fn index(idx: usize, arr: &[u8]) -> Option { if idx <= arr.len() { unsafe { Some(*arr.get_unchecked(idx)) @@ -44,7 +44,9 @@ Trickier than that is when we get into actual statefulness. Consider a simple implementation of `Vec`: ```rust -// Note this definition is insufficient. See the section on lifetimes. +use std::ptr; + +// Note this definition is insufficient. See the section on implementing Vec. pub struct Vec { ptr: *mut T, len: usize, @@ -61,21 +63,25 @@ impl Vec { self.reallocate(); } unsafe { - ptr::write(self.ptr.offset(len as isize), elem); + ptr::write(self.ptr.offset(self.len as isize), elem); self.len += 1; } } + + # fn reallocate(&mut self) { } } + +# fn main() {} ``` This code is simple enough to reasonably audit and verify. Now consider adding the following method: -```rust - fn make_room(&mut self) { - // grow the capacity - self.cap += 1; - } +```rust,ignore +fn make_room(&mut self) { + // grow the capacity + self.cap += 1; +} ``` This code is safe, but it is also completely unsound. Changing the capacity