Jonathan's Blog

Abstraction

Published on 2024-12-03

Despite saying previously that I was just going to use Rust for the Advent of Code this year, I did feel a slight twinge of regret that I was wasting an opportunity for learning a new language. Zig had piqued my interest recently and I tried my Rust in Zig. After an hour or two of reading the language documentation, looking through the Zig Cookbook, and attempting to write a solution I feel like I can confidently say that Zig is an interesting language that is entirely not for me.

The simple task of opening a file and reading through it line by line in Zig requires setting up allocators, getting a file handle, creating a reader, then wrapping that reader in a buffer reader, and then streaming the text into the buffer. Reading a single file a single time doesn’t need that level of abstraction. How fine-grained your control needs to be should be dictated by how performant that action must be. If I’m creating a hot loop that relies on constantly opening and closing files and then quickly streaming and parsing the bytes, then I want the ability to drop down to extremely low level control. In that situation we need little to no abstraction. In the majority of cases however, I don’t want to have to worry about the precise strategy I’m using to allocate chunks of memory.

Just as it’s useful for a language to allow stripping away as much abstraction as possible when necessary, it’s also useful for that language to allow using abstractions when complete control isn’t necessary. The problem with Zig is that it doesn’t allow more abstractions, you’re trapped at the lower levels. This is just as bad as trapping you at high levels of abstraction. You’re stuck with the opposite problem of Python or Ruby.

A good language should allow you to move up and down the layers of abstraction at will. This is why I love Rust. If I want to read a file, all I need is a single line of code using the read_to_string function and it’ll be fast enough for the vast majority of use cases. I know everything will “just work”. Rust also allows moving down the levels of abstraction as well. If I’m quickly reading small amounts of bytes, then BufReader can help. I can preallocate memory for vectors, pick between a half dozen available allocators, customize it, or even write my own. I don’t have to sacrifice either the low end or high end of abstraction.

Changes