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...
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'...
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...
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...
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...
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...
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...
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...
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...
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...
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...
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...
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...
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...
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...
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...
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...
RawVec We've actually reached an interesting situation here: we've duplicated the logic for specifying a buffer and freeing its memory in Vec and IntoIter. Now that we've implemented it and identified actual logic duplication, this is a goo...
Drain Let's move on to Drain. Drain is largely the same as IntoIter, except that instead of consuming the Vec, it borrows the Vec and leaves its allocation untouched. For now we'll only implement the "basic" full-range version. use std::mar...
Handling Zero-Sized Types It's time. We're going to fight the specter that is zero-sized types. Safe Rust never needs to care about this, but Vec is very intensive on raw pointers and raw allocations, which are exactly the two things that c...