fix all the doc tests
This commit is contained in:
parent
58f6f2d57a
commit
7aee8448ea
@ -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;
|
||||
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 = 10;
|
||||
// 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 x: [Box<u32>; SIZE];
|
||||
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));
|
||||
}
|
||||
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…
x
Reference in New Issue
Block a user