fix all the doc tests

pull/10/head
Alexis Beingessner 10 years ago committed by Manish Goregaokar
parent 4b9d71becf
commit 9f20f453b2

@ -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 {

@ -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<X: Trait>(t: X) {}

@ -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<T>{ 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<T>{ ptr: Unique<T> }
impl<T> Drop for Box<T> {
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::<T>(),
mem::align_of::<T>());
}
}
}
@ -42,25 +51,36 @@ after-free the `ptr` because the Box is immediately marked as uninitialized.
However this wouldn't work:
```rust
struct Box<T>{ 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<T>{ ptr: Unique<T> }
impl<T> Drop for Box<T> {
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::<T>(),
mem::align_of::<T>());
}
}
}
struct SuperBox<T> { box: Box<T> }
struct SuperBox<T> { my_box: Box<T> }
impl<T> Drop for SuperBox<T> {
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::<T>(),
mem::align_of::<T>());
}
}
}
@ -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<T>{ 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<T>{ ptr: Unique<T> }
impl<T> Drop for Box<T> {
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::<T>(),
mem::align_of::<T>());
}
}
}
struct SuperBox<T> { box: Option<Box<T>> }
struct SuperBox<T> { my_box: Option<Box<T>> }
impl<T> Drop for SuperBox<T> {
fn drop(&mut self) {
@ -125,7 +154,11 @@ impl<T> Drop for SuperBox<T> {
// 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::<T>(),
mem::align_of::<T>());
mem::forget(my_box);
}
}
}

@ -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);

@ -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.

@ -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<F> {
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.
have a nice magic sugar for the common cases.

@ -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<T> {
ptr: *mut RcBox<T>,
}
@ -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(|| {

@ -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
```
```

@ -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<T> {
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,

@ -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<i32> = 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.
are too dumb.

@ -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];

@ -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<u16, u32> {
count: u16,
data1: u16,

@ -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 {}

@ -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<E>, // variant over E
f: Cell<F>, // invariant over F
g: G // variant over G
h1: H // would also be variant over H except...
h2: Cell<H> // invariant over H, because invariance wins
g: G, // variant over G
h1: H, // would also be variant over H except...
h2: Cell<H>, // invariant over H, because invariance wins
}
```
```

@ -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;
```

@ -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<u32>; 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<u32>; 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.
it around at all, be sure to be *really* careful.

@ -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 {

@ -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<T> Drop for Vec<T> {
fn drop(&mut self) {
if self.cap != 0 {
while let Some(_) = self.pop() { }
let align = mem::min_align_of::<T>();
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>();
let num_bytes = elem_size * self.cap;
unsafe {

@ -9,7 +9,7 @@ conditions.
All we need is `slice::from_raw_parts`.
```rust
```rust,ignore
use std::ops::Deref;
impl<T> Deref for Vec<T> {
@ -24,7 +24,7 @@ impl<T> Deref for Vec<T> {
And let's do DerefMut too:
```rust
```rust,ignore
use std::ops::DerefMut;
impl<T> DerefMut for Vec<T> {

@ -51,7 +51,7 @@ impl<T> RawValIter<T> {
And IntoIter becomes the following:
```
```rust,ignore
pub struct IntoIter<T> {
_buf: RawVec<T>, // we don't actually care about this. Just need it to live.
iter: RawValIter<T>,
@ -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<T> RawVec<T> {
fn new() -> Self {
unsafe {
@ -194,7 +194,7 @@ impl<T> RawVec<T> {
// 0, getting to here necessarily means the Vec is overfull.
assert!(elem_size != 0, "capacity overflow");
let align = mem::min_align_of::<T>();
let align = mem::align_of::<T>();
let (new_cap, ptr) = if self.cap == 0 {
let ptr = heap::allocate(elem_size, align);
@ -223,7 +223,7 @@ impl<T> Drop for RawVec<T> {
// don't free zero-sized allocations, as they were never allocated.
if self.cap != 0 && elem_size != 0 {
let align = mem::min_align_of::<T>();
let align = mem::align_of::<T>();
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<T> RawValIter<T> {
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<T> Iterator for RawValIter<T> {
type Item = T;
fn next(&mut self) -> Option<T> {
@ -315,4 +315,4 @@ impl<T> DoubleEndedIterator for RawValIter<T> {
}
```
And that's it. Iteration works!
And that's it. Iteration works!

@ -38,7 +38,7 @@ impl<T> RawVec<T> {
// 0, getting to here necessarily means the Vec is overfull.
assert!(elem_size != 0, "capacity overflow");
let align = mem::min_align_of::<T>();
let align = mem::align_of::<T>();
let (new_cap, ptr) = if self.cap == 0 {
let ptr = heap::allocate(elem_size, align);
@ -65,7 +65,7 @@ impl<T> Drop for RawVec<T> {
fn drop(&mut self) {
let elem_size = mem::size_of::<T>();
if self.cap != 0 && elem_size != 0 {
let align = mem::min_align_of::<T>();
let align = mem::align_of::<T>();
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);
}
```
# fn main() {}
```

@ -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
}
}
```
```

@ -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<T> {
buf: Unique<T>,
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<T> Vec<T> {
fn into_iter(self) -> IntoIter<T> {
// Can't destructure Vec since it's Drop
@ -93,7 +93,7 @@ impl<T> Vec<T> {
Here's iterating forward:
```rust
```rust,ignore
impl<T> Iterator for IntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<T> {
@ -118,7 +118,7 @@ impl<T> Iterator for IntoIter<T> {
And here's iterating backwards.
```rust
```rust,ignore
impl<T> DoubleEndedIterator for IntoIter<T> {
fn next_back(&mut self) -> Option<T> {
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<T> Drop for IntoIter<T> {
fn drop(&mut self) {
if self.cap != 0 {
// drop any remaining elements
for _ in &mut *self {}
let align = mem::min_align_of::<T>();
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>();
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<T> {
ptr: Unique<T>,
@ -182,7 +182,7 @@ impl<T> RawVec<T> {
// unchanged from Vec
fn grow(&mut self) {
unsafe {
let align = mem::min_align_of::<T>();
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>();
let (new_cap, ptr) = if self.cap == 0 {
@ -210,7 +210,7 @@ impl<T> RawVec<T> {
impl<T> Drop for RawVec<T> {
fn drop(&mut self) {
if self.cap != 0 {
let align = mem::min_align_of::<T>();
let align = mem::align_of::<T>();
let elem_size = mem::size_of::<T>();
let num_bytes = elem_size * self.cap;
unsafe {
@ -223,7 +223,7 @@ impl<T> Drop for RawVec<T> {
And change vec as follows:
```rust
```rust,ignore
pub struct Vec<T> {
buf: RawVec<T>,
len: usize,
@ -254,14 +254,14 @@ impl<T> Drop for Vec<T> {
And finally we can really simplify IntoIter:
```rust
```rust,ignore
struct IntoIter<T> {
_buf: RawVec<T>, // 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<T> Drop for IntoIter<T> {
fn drop(&mut self) {
@ -290,4 +290,4 @@ impl<T> Vec<T> {
}
```
Much better.
Much better.

@ -4,11 +4,13 @@ First off, we need to come up with the struct layout. Naively we want this
design:
```rust
struct Vec<T> {
pub struct Vec<T> {
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<T> {
cap: usize,
len: usize,
}
# fn main() {}
```
As a recap, Unique is a wrapper around a raw pointer that declares that:

@ -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<T> {
if self.len == 0 {
None
@ -52,4 +52,4 @@ pub fn pop(&mut self) -> Option<T> {
}
}
}
```
```

@ -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<u8> {
fn index(idx: usize, arr: &[u8]) -> Option<u8> {
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<u8> {
fn index(idx: usize, arr: &[u8]) -> Option<u8> {
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<T> {
ptr: *mut T,
len: usize,
@ -61,21 +63,25 @@ impl<T> Vec<T> {
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

Loading…
Cancel
Save