Bracket pattern in Rust

2020-03-25

Another Rust-related quickie today!

Much like C++, Rust encourages the Resource Acquisition is Initialization pattern: whenever an object goes out of scope, its destructor (drop in Rust) is called and its owned resources are freed. Possibly as a consequence of this, the Rust programming language (like C++) does not include the equivalent of a finally construct as found in languages like Java and C#. In most situations, Rust’s standard RAII is perfectly adequate. A downside I’ve encountered is that some resources or resource-like things do not implement the Drop trait. To work around this, we have to introduce additional structs which do have implementations for Drop to manage these resources. I’ll work through an example below.

Here’s a simple resource:

Here’s an example illustrating how one might (naively) attempt to manage the resource:

In the presence of errors, this code will leak the resource since an early return will prevent the clean-up code from running. The resource leak is obvious in this example (and obvious to the compiler—hence the #[allow(unreachable_code)] annotation), but this is not always the case. Use of the ? operator will obscure early returns in ways that are not obvious to the developer or the compiler.

If we owned the Resource struct, then we could, of course, directly provide an implementation of Drop for it and most of the problems would go away. Imagine if you will, however, that this resource is defined by another package. In this situation Rust will prevent us from defining an implementation of somebody else’s trait (i.e. Drop in this case) for it (this is Rust’s orphan rule which is equivalent to Haskell’s orphan instance rule). We can address this by introducing a wrapper (named ResourceHolder in this example) with an implementation for Drop which will release the resource even in the presence of errors:

This is perfectly acceptable. Note that you have to assign the resource holder to a name (_holder in this case) in order for its lifetime to extend to the end of the enclosing lexical scope—note that the “throwaway” name _ is not sufficient since the compiler will generate code to release this kind of value immediately and release the resource prematurely.

I’m not a big fan of introducing this kind of unused variable, largely because the intent is not clear. To the casual observer, the name might seem insignificant and someone might mistakenly change the name to _ in the future which would drastically change the behaviour of the code (by releasing the resource prematurely). We can eliminate this problem by giving it a more meaningful (non-underscore) name and explicitly calling the std::mem::drop function:

Now it’s clear what the variable name, holder, is for and it is clear that holder must live until the call to drop (and no further, since drop consumes its argument). The drop version still requires this additional holder type which also bothers me.

An approach I like, which addresses all of these concerns, is to introduce the bracket function (heavily inspired by Haskell):

And here’s how it’s used:

This GitHub project is a full working example project demonstrating these approaches.

Related posts

String conversions in Rust follow-up
String conversions in Rust
Boilerplate for Rust error/result
Parsing MSBuild project XML in Rust
Traits and polymorphism in Rust
Richard’s Workspace Tool - more Rust programming

Tags

Rust
Programming

Content © 2024 Richard Cook. All rights reserved.