Cover image for Crafting an Interpreter in Zig - part 1

Posted on • Updated on

Crafting an Interpreter in Zig - part 1

Few weeks ago I came with the idea of learning Zig. I went to ziglearn and read it all, but I felt I needed something more hands on, something that let me explore and discover the language on my own.

After some though I decided to give Crafting Interpreters a try, it has always been on my reading list, it was the time. Crafting interpreters is a book where you implement two versions of the same language, called Lox, using two different techniques. The III part of the book implements the Lox programming language using C, and I though it would be the ideal project to learn more about Zig and interpreters, two birds one rock!

I want to write my learnings and findings during this journey, my idea is to have a post for each chapter of the book highlighting specific things that I personally found interesting about Zig and how it differs from the solution proposed by the book. I will probably not talk much about interpreters or compilers since I now next to nothing about it. You can find the code here https://github.com/avillega/zilox. I decided to name my version of the interpreter zilox given that the original version is called clox. If you do some pronunciation games you can pronounce both of them as see-lox.

One of the first few things the author does in the first chapter of the III part is to implement a dynamic sized array for storing bytes. Awesome! Zig has an ArrayList struct as part of the std, but the goal here is to learn so I decided to follow along and try to implement my own dynamic sized array to store u8, all went well and good until I found this function signature of the realloc function in Allocator.zig

pub fn realloc(self: *Allocator, old_mem: anytype, new_n: usize) t: {
    const Slice = @typeInfo(@TypeOf(old_mem)).Pointer;
    break :t Error![]align(Slice.alignment) Slice.child;
Enter fullscreen mode Exit fullscreen mode

I asked what that return type means in the zig sub reddit r/zig and they broke it our for me. Basically the block after the t: is defining t, duh (It was not easy to me to understand that at first), and the block is saying, "I expect the type of old_mem to be a pointer type and will return a Slice of the same type and same aligment as old_mem. if you have a slice of type []u8 Slice.child will be u8.

The second thing the author does in the same chapter is to implement a second dynamic sized array, this time to store f64 values. What! again? C story of generic programming is not the best, but in Zig that is not the case. I decided to go with my limited knowledge and implement a generic dynamic sized array that I can reuse to store u8, f64 and any other type. I got to say that implementing this made generic programming in Zig click for me, it is powerful, it is simple, it is elegant, no weird <T> every where, no special syntax, just plain old functions that take types as arguments and return a specific type. This is basically what I defined.

pub fn DynamicArray(comptime T: type) type { ... }
Enter fullscreen mode Exit fullscreen mode

And this is how I use it.

const Value = f64;
const BytesArray = DynamicArray(u8);
const ValuesArray = DynamicArray(Value);
Enter fullscreen mode Exit fullscreen mode

And guess what the author does third, exactly!, another dynamic sized array, this time to store the line numbers of the Lox program. With my generic DynamicArray implemented, it was as easy as doing.

const LinesArray = DynamicArray(u16);
Enter fullscreen mode Exit fullscreen mode

Almost a whole sub section of the chapter, becomes a couple lines of code.

So far I've like the experience of writing Zig a lot, I've gone through the code of the std to learn and to understand more about Allocators, Dynamic Arrays (ArrayLists), strings, formatting, etc. The std code is very approachable and comments are very useful. I personally have found reading the source code a bit more helpful in understanding what is going on than the docs themselves.

I hope you find this article helpful and see you in the next one.

Cover Photo by Ryutaro Tsukata from Pexels.

Discussion (4)

javier profile image

this is a great exercise! i did the same a couple of years ago. back then the book wasn't completed, and Zig was different too... but it lets you really appreciate how Zig improves on C while keeping the same "hands on" feeling (but the standard library has grown quite a bit too!)

ityonemo profile image
Isaac Yonemoto • Edited on

while my mind is blown by the realloc return signature i wonder if it wouldn't be more readable as a function call?

fn reallocReturnFor(comptime T: type) type {
  const Slice = @typeInfo(T).Pointer;
  return Error![]align(Slice.alignment) Slice.child;

pub fn realloc(self: *Allocator, old_mem: anytype, new_n: usize) reallocReturnFor(@TypeOf(old_mem) {
Enter fullscreen mode Exit fullscreen mode
david_vanderson profile image
David Vanderson

That function signature always looked weird to me too - thanks for explaining it!

Also you can use

Enter fullscreen mode Exit fullscreen mode

here to colorize codeblocks if you want.

andres profile image
Andres Author

I am doing this for my new posts, Thanks!