# RAII Guards and Newtypes in Rust


I’ve been having a bunch of fun in Rust recently. I’ve finally gotten past the
point of fighting with the borrow checker and now am solidly in the plateau of
productivity with the language.[^1] One of the first things that struck me about
Rust was how confident I felt that if I wrote something sane-looking that passed
the compiler, I was more likely than in other languages to have a program that
_actually worked_. The borrow checker does a lot of the heavy lifting here, of
course. But there are some other useful language conventions that help too.

One trick I’ve been reaching for is
[RAII Guards](https://rust-unofficial.github.io/patterns/patterns/behavioural/RAII.html).
Per the unofficial Rust Design Patterns book:

> RAII stands for “Resource Acquisition is Initialisation” which is a terrible
> name. The essence of the pattern is that resource initialisation is done in
> the constructor of an object and finalisation in the destructor. This pattern
> is extended in Rust by using a RAII object as a guard of some resource and
> relying on the type system to ensure that access is always mediated by the
> guard object.

If you’ve used `std::fs::File` or `std::sync::Mutex`, you’ve already seen this
pattern! The idea is that we can use a few properties of the Rust compiler to
deterministically and safely handle resources. Those properties are: lifetime
tracking and deterministic destruction.

Take an example for `std::fs::File`. The `File` struct that you get back from
`File::open` has an implementation of `Drop` on it that automatically closes the
file descriptor that is opened by `File::open`:

```rust
// Pseudocode File implementation.
struct File {
    handle: RawDescriptor, // The OS-level file descriptor
}

impl File {
    fn open(path: String) -> File {
        // ACQUISITION: Syscall to get the OS-level file descriptor.
        let h = os::open_file_handle(path);
        File { handle: h }
    }
}

impl Drop for File {
    fn drop(&mut self) {
        // RELEASE: Called when the variable goes out of scope.
        os::close_file_handle(self.handle);
    }
}

// Usage Example
{
    let my_file = File::open("data.txt");
    my_file.write("Hello");
} // <--- my_file goes out of scope here; drop() is called deterministically.
```

The key thing here is that Rust calls destructors deterministically when a
variable goes out of scope. So, even though this looks a bit “magical”, there
isn’t any runtime ambiguity. Precisely when `my_file` goes out of scope, the
file descriptor is closed. This is in contrast to garbage-collected languages,
where you don’t have a strong guarantee of when object destructors are called.

We can use this deterministic destructor behavior to construct useful runtime
behavior. For example, the `MutexGuard` that `Mutex::lock` returns uses this
deterministic `Drop` call to unlock the `Mutex`:

```rust
// Pseudocode Mutex<T> implementation.
struct Mutex<T> {
    data: UnsafeCell<T>,
    is_locked: AtomicBool,
}

// The RAII Guard
struct MutexGuard<'a, T> {
    mutex_ref: &'a Mutex<T>, // Holds a reference to the parent Mutex
}

impl<T> Mutex<T> {
    fn lock(&self) -> MutexGuard<T> {
        // ACQUISITION: Block until the lock is acquired
        while self.is_locked.swap(true, Ordering::Acquire) { /* spin */ }

        // Return the Guard that tracks this state
        MutexGuard { mutex_ref: self }
    }
}

impl<'a, T> Drop for MutexGuard<'a, T> {
    fn drop(&mut self) {
        // RELEASE: Reset the lock state on the parent Mutex
        self.mutex_ref.is_locked.store(false, Ordering::Release);
    }
}

// Usage Example
{
    let guard = my_mutex.lock(); // Mutex is now locked
    *guard += 1;                 // Access data safely
} // <--- guard goes out of scope; drop() runs, Mutex is now unlocked for others.
```

What I find powerful about this pattern is that you are using the _type system_
of the language to ensure correctness. It’s like the idea of
“[making illegal states unrepresentable](https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/)”.
Using the above design, it’s _surprisingly hard_ to accidentally grab a mutex
lock and cause a deadlock by failing to later unlock it.

## Newtype

RAII Guards pair well with the
[Newtype](https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html)
pattern to add yet more safety by utilizing the type system. The idea of the
Newtype pattern is pretty simple: wrap an existing type in a thin wrapper struct
to change its capabilities. I’ve
[written about this pattern before](/blog/2021/11/14/Using-Embedding-to-Disambiguate-Types-in-Go/)
about Golang, where this is often done via
[Type Embedding](https://go.dev/doc/effective_go#embedding).

This pairs nicely with RAII guards: often when you are using a guard, you want
to lock down the resource to prevent unintentional misuse. For example, say
you're working with a database connection handle. The underlying type might just
be a low-level handle representing an OS-level resource, and you want to prevent
it from being accidentally duplicated (which could lead to connection reuse or
aliasing bugs).

Here is a quite naive approach:

```rust
struct ConnectionPool {
    connections: Mutex<Vec<u64>>, // Raw OS handles
}

impl ConnectionPool {
    fn checkout(&self) -> u64 {
        let mut conns = self.connections.lock().unwrap();
        conns.pop().expect("no connections available")
    }

    fn checkin(&self, handle: u64) {
        self.connections.lock().unwrap().push(handle);
    }
}
```

The problem: since `u64` has `Copy`, nothing stops you from doing this:

```rust
let handle = pool.checkout();
let oops_handle = handle;
pool.checkin(handle);

client.query(oops_handle, "SELECT * FROM users;"); // Whoops!
```

This is of course rather contrived, but it shows how if we’re relying on the end
programmer to remember to “return” resources that they’ve checked out, we are
open to a rich source of leakage and misuse. However, we can fix this by
wrapping the raw handle in a newtype that doesn't implement `Copy` or `Clone`:

```rust
struct ConnectionHandle(u64); // Not Copy, not Clone

struct ConnectionPool {
    connections: Mutex<Vec<u64>>,
}

impl ConnectionPool {
    fn checkout(&self) -> ConnectionHandle {
        let mut conns = self.connections.lock().unwrap();
        ConnectionHandle(conns.pop().expect("no connections available"))
    }

    fn checkin(&self, handle: ConnectionHandle) {
        self.connections.lock().unwrap().push(handle.0);
    }
}
```

Now the compiler prevents the bug:

```rust
let handle = pool.checkout();
let oops_handle = handle; // This is a move, not a copy
pool.checkin(handle); // Error: handle was already moved to `oops_handle`
```

In this example, the newtype pattern forces single-ownership semantics onto a
primitive that would otherwise be freely copyable. You also could go further and
combine this with an RAII guard that automatically checks the connection back in
when dropped.

The meta lesson here is that a feature-rich language like Rust has strong
benefits. Yes, you’ll sometimes need to decipher some rather verbose type
signatures like
`Arc<RwLock<HashMap<TypeId, Box<dyn FnMut(&mut dyn Any) + Send>>>>`, but in
exchange you get your friendly compiler to prevent you from footgunning yourself
in myriad ways.

In the learning pit of despair, Rust’s type system can feel like an ivory tower.
I'd read one of FasterThanLime’s
[fantastic articles](https://fasterthanli.me/articles/catching-up-with-async-rust),
but after I'd digested the great technical writing, I was left with a feeling of
“OK, but now I need to return to my day job and write some enterprise software
glue code”. Once at the plateau of productivity, the type/memory/structural
safety you’ve been slowly absorbing more than pays for the initial discomfort.

[^1]:
    The final boss, as it turns out, was allowing myself to use a healthy
    quantity of `Arc<T>`s.

