Vale's Higher RAII

ravenical1 pts0 comments

Vale's Higher RAII, the pattern that saved me a vital 5 hours in the 7DRL Challenge

Languages ∩ Architecture

RSS<br>Github<br>r/Vale<br>Twitter<br>Discord

Vale's Higher RAII, the pattern that saved me a vital 5 hours in the 7DRL Challenge

Mar 22, 2022

Evan Ovadia

This year's 7-Day Roguelike Challenge has just come to an end, and 202 new roguelike games have been suddenly released into the world. Hundreds of 7DRL developers are leaning back, relaxing after an intense week of coding, debugging, and playtesting.

Only 20% of entries end up successful, because it's surprisingly difficult to make an entire roguelike game within 7 days. I attempted the challenge, and after an epic seven days, slid into a finish 15 minutes before the deadline. 0

In such a short challenge, every hour counts. One has to be careful to keep scope down, and employ good tools to keep bugs away and keep debugging time down. Today, I'll describe one of the tools that saved me some critical hours near the end.

This was the first year I used the Vale programming language, a language I've been contributing to for quite a while.

Vale is bringing three innovations into the programming languages world:

Generational references, where the compiler will make sure the object is alive when we dereference a reference to it. 1

The region borrow checker (mentioned in Seamless, Fearless, and Structured Concurrency) which improves upon Rust's borrow checker to handle shared mutable objects.

Higher RAII , where we use linear types to make sure we explicitly call a function at some point in the future, even past the current scope.

Let's hear more about that last one!

Side Notes

(interesting tangential thoughts)

Notes [–]<br>Notes [+]

You can find the game here, but be warned, it's just a prototype of using Vale, so not many fun things were added to it!

Generational references are faster than reference counting, and gives us control over our memory layouts for better performance. It's also more flexible than borrow checking, and gives us more freedom with our architectures!

Higher RAII

Higher RAII is based on C++'s "RAII" 2 which automatically calls a zero-argument non-returning function (often called a "destructor") just before we destroy an object. Higher RAII does that, plus much more.

With Higher RAII, we can ensure that we eventually call any function... even ones with parameters, return values, even ones that don't destroy the object.

Imagine that you're dropping your laptop off at a repair shop. They tie a wristband with an ID number around your wrist. This is a "Higher RAII" wristband, in that:

You're not able to remove this wristband.

They will remove this wristband from your wrist when you pick your laptop up.

When you get home, you absentmindedly try to remove the wristband, and suddenly realize that you forgot to pick up your laptop. So you get back in your car, and go exchange the wristband for the laptop.

With Higher RAII, you can turn any object into a wristband, and the compiler will enforce that you get rid of it correctly instead of just destroying it.

Notes [–]<br>Notes [+]

RAII stands for Resource Acquisition is Initialization, and was developed for C++ primarily by Bjarne Stroustrup and Andrew Koenig. Other languages like D and Rust also offer it in some form.

A Real World Example

C++ uses a promise to send data to another thread's future. For example:

Thread A calls myIntPromise.set_value(1337).

Thread B calls myIntFuture.get() which waits and receives that 1337.

The programmer must remember to call set_value. However, sometimes the programmer forgets, and then thread B waits forever.

In Vale, we can use Higher RAII to enforce that we set the value before destroying the promise:

vale

#!DeriveStructDrop<br>struct PromiseT> {<br>future &FutureT>;<br>func SetValueAndDestroyT>(self PromiseT>, new_value T) {<br>set self.future.value = new_value;<br>destruct self;

Here's how that works.

In Vale, every object has exactly one owning reference pointing to it. For example, the parameter self Promise is an owning reference. future &Future is not an owning reference, because it has a &.

The compiler normally automatically destructs the object when we destroy its owning reference's containing scope (or containing object). However, the #!DeriveStructDrop instructs the compiler to never automatically do that, but instead throw a compile error:

vale

func main() {<br>promise Promiseint> = ...;

// Here, since promise still exists, compiler will try to call its `drop` function.<br>// Compile error: No function named `drop` exists!

Now, the user must do something with the owning reference. Thats where SetValueAndDestroy comes in: it will take the owning reference and destruct it, as well as set the future's value:

vale

func main() {<br>promise Promiseint> = …;

SetValueAndDestroy(promise, 1337);<br>// The above line moved the promise local variable into the<br>// SetValueAndDestroy function, so it no longer exists here.

As you can see, the compiler now...

raii vale higher reference promise object

Related Articles