mirror of https://github.com/rust-lang/nomicon
parent
736f072c13
commit
aca268f272
@ -0,0 +1,141 @@
|
||||
% Example: Implementing Vec
|
||||
|
||||
To bring everything together, we're going to write `std::Vec` from scratch.
|
||||
Because the all the best tools for writing unsafe code are unstable, this
|
||||
project will only work on nightly (as of Rust 1.2.0).
|
||||
|
||||
First off, we need to come up with the struct layout. Naively we want this
|
||||
design:
|
||||
|
||||
```
|
||||
struct Vec<T> {
|
||||
ptr: *mut T,
|
||||
cap: usize,
|
||||
len: usize,
|
||||
}
|
||||
```
|
||||
|
||||
And indeed this would compile. Unfortunately, it would be incorrect. The compiler
|
||||
will give us too strict variance, so e.g. an `&Vec<&'static str>` couldn't be used
|
||||
where an `&Vec<&'a str>` was expected. More importantly, it will give incorrect
|
||||
ownership information to dropck, as it will conservatively assume we don't own
|
||||
any values of type `T`. See [the chapter on ownership and lifetimes]
|
||||
(lifetimes.html) for details.
|
||||
|
||||
As we saw in the lifetimes chapter, we should use `Unique<T>` in place of `*mut T`
|
||||
when we have a raw pointer to an allocation we own:
|
||||
|
||||
|
||||
```
|
||||
#![feature(unique)]
|
||||
|
||||
use std::ptr::Unique;
|
||||
|
||||
pub struct Vec<T> {
|
||||
ptr: Unique<T>,
|
||||
cap: usize,
|
||||
len: usize,
|
||||
}
|
||||
```
|
||||
|
||||
As a recap, Unique is a wrapper around a raw pointer that declares that:
|
||||
|
||||
* We own at least one value of type `T`
|
||||
* We are Send/Sync iff `T` is Send/Sync
|
||||
* Our pointer is never null (and therefore `Option<Vec>` is null-pointer-optimized)
|
||||
|
||||
That last point is subtle. First, it makes `Unique::new` unsafe to call, because
|
||||
putting `null` inside of it is Undefined Behaviour. It also throws a
|
||||
wrench in an important feature of Vec (and indeed all of the std collections):
|
||||
an empty Vec doesn't actually allocate at all. So if we can't allocate,
|
||||
but also can't put a null pointer in `ptr`, what do we do in
|
||||
`Vec::new`? Well, we just put some other garbage in there!
|
||||
|
||||
This is perfectly fine because we already have `cap == 0` as our sentinel for no
|
||||
allocation. We don't even need to handle it specially in almost any code because
|
||||
we usually need to check if `cap > len` or `len > 0` anyway. The traditional
|
||||
Rust value to put here is `0x01`. The standard library actually exposes this
|
||||
as `std::rt::heap::EMPTY`. There are quite a few places where we'll want to use
|
||||
`heap::EMPTY` because there's no real allocation to talk about but `null` would
|
||||
make the compiler angry.
|
||||
|
||||
All of the `heap` API is totally unstable under the `alloc` feature, though.
|
||||
We could trivially define `heap::EMPTY` ourselves, but we'll want the rest of
|
||||
the `heap` API anyway, so let's just get that dependency over with.
|
||||
|
||||
So:
|
||||
|
||||
```rust
|
||||
#![feature(alloc)]
|
||||
|
||||
use std::rt::heap::EMPTY;
|
||||
use std::mem;
|
||||
|
||||
impl<T> Vec<T> {
|
||||
fn new() -> Self {
|
||||
assert!(mem::size_of::<T>() != 0, "We're not ready to handle ZSTs");
|
||||
unsafe {
|
||||
// need to cast EMPTY to the actual ptr type we want, let
|
||||
// inference handle it.
|
||||
Vec { ptr: Unique::new(heap::EMPTY as *mut _), len: 0, cap: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
I slipped in that assert there because zero-sized types will require some
|
||||
special handling throughout our code, and I want to defer the issue for now.
|
||||
Without this assert, some of our early drafts will do some Very Bad Things.
|
||||
|
||||
Next we need to figure out what to actually do when we *do* want space. For that,
|
||||
we'll need to use the rest of the heap APIs. These basically allow us to
|
||||
talk directly to Rust's instance of jemalloc.
|
||||
|
||||
We'll also need a way to handle out-of-memory conditions. The standard library
|
||||
calls the `abort` intrinsic, but calling intrinsics from normal Rust code is a
|
||||
pretty bad idea. Unfortunately, the `abort` exposed by the standard library
|
||||
allocates. Not something we want to do during `oom`! Instead, we'll call
|
||||
`std::process::exit`.
|
||||
|
||||
```rust
|
||||
fn oom() {
|
||||
::std::process::exit(-9999);
|
||||
}
|
||||
```
|
||||
|
||||
Okay, now we can write growing:
|
||||
|
||||
```rust
|
||||
fn grow(&mut self) {
|
||||
unsafe {
|
||||
let align = mem::min_align_of::<T>();
|
||||
let elem_size = mem::size_of::<T>();
|
||||
|
||||
let (new_cap, ptr) = if self.cap == 0 {
|
||||
let ptr = heap::allocate(elem_size, align);
|
||||
(1, ptr)
|
||||
} else {
|
||||
let new_cap = 2 * self.cap;
|
||||
let ptr = heap::reallocate(*self.ptr as *mut _,
|
||||
self.cap * elem_size,
|
||||
new_cap * elem_size,
|
||||
align);
|
||||
(new_cap, ptr)
|
||||
};
|
||||
|
||||
// If allocate or reallocate fail, we'll get `null` back
|
||||
if ptr.is_null() { oom() }
|
||||
|
||||
self.ptr = Unique::new(ptr as *mut _);
|
||||
self.cap = new_cap;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There's nothing particularly tricky in here: if we're totally empty, we need
|
||||
to do a fresh allocation. Otherwise, we need to reallocate the current pointer.
|
||||
Although we have a subtle bug here with the multiply overflow.
|
||||
|
||||
TODO: rest of this
|
||||
|
||||
|
Loading…
Reference in new issue