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

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

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

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

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

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

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

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

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