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