IntoIter Let's move on to writing iterators. iter and iter_mut have already been written for us thanks to The Magic of Deref. However there's two interesting iterators that Vec provides that slices can't: into_iter and drain. IntoIter consu...
Insert and Remove Something not provided by slice is insert and remove, so let's do those next. Insert needs to shift all the elements at the target index to the right by one. To do this we need to use ptr::copy, which is our version of C's...
Deref Alright! We've got a decent minimal stack implemented. We can push, we can pop, and we can clean up after ourselves. However there's a whole mess of functionality we'd reasonably want. In particular, we have a proper array, but none o...
Deallocating Next we should implement Drop so that we don't massively leak tons of resources. The easiest way is to just call pop until it yields None, and then deallocate our buffer. Note that calling pop is unneeded if T: !Drop. In theory...
Push and Pop Alright. We can initialize. We can allocate. Let's actually implement some functionality! Let's start with push. All it needs to do is check if we're full to grow, unconditionally write to the next index, and then increment our...
Allocating Memory Using NonNull throws a wrench in an important feature of Vec (and indeed all of the std collections): creating an empty Vec doesn't actually allocate at all. This is not the same as allocating a zero-sized memory block, wh...
Layout First off, we need to come up with the struct layout. A Vec has three parts: a pointer to the allocation, the size of the allocation, and the number of elements that have been initialized. Naively, this means we just want this design...
Example: Implementing Vec To bring everything together, we're going to write std::Vec from scratch. We will limit ourselves to stable Rust. In particular we won't use any intrinsics that could make our code a little bit nicer or efficient b...
Atomics Rust pretty blatantly just inherits the memory model for atomics from C++20. This is not due to this model being particularly excellent or easy to understand. Indeed, this model is quite complex and known to have several flaws. Rath...
Send and Sync Not everything obeys inherited mutability, though. Some types allow you to have multiple aliases of a location in memory while mutating it. Unless these types use synchronization to manage this access, they are absolutely not...
Data Races and Race Conditions Safe Rust guarantees an absence of data races, which are defined as: • two or more threads concurrently accessing a location of memory • one or more of them is a write • one or more of them is unsynchronized A...
Concurrency and Parallelism Rust as a language doesn't really have an opinion on how to do concurrency or parallelism. The standard library exposes OS threads and blocking sys-calls because everyone has those, and they're uniform enough tha...
Poisoning Although all unsafe code must ensure it has minimal exception safety, not all types ensure maximal exception safety. Even if the type does, your code may ascribe additional meaning to it. For instance, an integer is certainly exce...
Exception Safety Although programs should use unwinding sparingly, there's a lot of code that can panic. If you unwrap a None, index out of bounds, or divide by 0, your program will panic. On debug builds, every arithmetic operation can pan...
Unwinding Rust has a tiered error-handling scheme: • If something might reasonably be absent, Option is used. • If something goes wrong and can reasonably be handled, Result is used. • If something goes wrong and cannot reasonably be handle...
Leaking Ownership-based resource management is intended to simplify composition. You acquire resources when you create the object, and you release the resources when it gets destroyed. Since destruction is handled for you, it means you can'...
Destructors What the language does provide is full-blown automatic destructors through the Drop trait, which provides the following method: fn drop(&mut self); This method gives the type time to somehow finish what it was doing. After drop...
Constructors There is exactly one way to create an instance of a user-defined type: name it, and initialize all its fields at once: struct Foo { a: u8, b: u32, c: bool, } enum Bar { X(u32), Y(bool), } struct Unit; let foo = Foo { a: 0, b: 1...
The Perils Of Ownership Based Resource Management (OBRM) OBRM (AKA RAII: Resource Acquisition Is Initialization) is something you'll interact with a lot in Rust. Especially if you use the standard library. Roughly speaking the pattern is as...
Unchecked Uninitialized Memory One interesting exception to this rule is working with arrays. Safe Rust doesn't permit you to partially initialize an array. When you initialize an array, you can either set every value to the same thing with...