Giving Zterm Some Love

Quite a bit has happened code wise in the past couple of weeks since my last post. Most of which has been in Zig and in support of Zterm.

Porting to Zig-0.9.0

There were a small number of breaking changes in Zig between the last time I had worked on Zterm and the release of 0.9.0. One of them in particular bit me pretty hard, but there were three which affected this codebase directly.


The allocgate issue was a change in the allocator interface due to some less than optimal interaction between Zig and llvm. I'm not going to go into depth because the article which I linked to explains it better than I could. Instead, I'll just point out the practical ramifications for most Zig projects. Whereas before, anywhere that one needed to allocate memory it was necessary to pass in a pointer to an Allocator struct, now that struct is passed in by value. In most cases this was an easy fix and it's actually very instructional to see just how many allocations are being made. Which is one of the things that I truly do love about Zig - the fact that allocations are explicit makes you think about how to keep the overall number of them down, which tends to lead to leaner and faster code.

Another thing which changed due to this change in std is that what was previously a struct field has now become a function which returns the allocator itself. Here's part of a diff showing some changes:

 @@ -10,7 +10,7 @@ const WriteError = std.os.WriteError;
 const File = std.fs.File;
 const Allocator = std.mem.Allocator;

-var allocator: *Allocator = undefined;
+var allocator: Allocator = undefined;

 const logger = std.log.scoped(.cli);
 // pub const log_level = std.log.Level.debug;
 @@ -229,7 +229,7 @@ pub fn main() u8 {
     // Use an arena allocator - no need to free memory as we go.
     var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
     defer arena.deinit();
-    allocator = &arena.allocator;
+    allocator = arena.allocator();
     const rc = mainWorker() catch 1;
     logger.debug("{d:6} Exiting with: {d}", .{ elapsed(), rc });
     return rc;

Again, what was fun about this particular breaking change was not the work in fixing each case, but the total overal instances. Pretty much anyone depending on other packages had to go through their dependencies and help fix things. I contributed back to zig-nestedtext on this one, but many people helped to get things running again over the entire ecosystem.


This one bit me pretty hard. The declaration usingnamespace that mixes all of the declarations of the operand into the current namespace. What changed is that previously I could just do this:

usingnamespace @import("somefile.zig");

And I could access anything in "somefile.zig" as if it were part of the current file. This is no longer the case. Now, the primary usage is to forward declarations as the symbols are not available in the file using usingnamespace without explicitly assigning them to a constant. As this was being used extensively in zig-vte, there were a large number of constants to declare where previously everything needed was pulled in just by using usingnamespace.

c_void renamed anyopaque

This one was only significant because of how late in the development cycle it snuck in. The term c_void was somewhat misleading in the first place, as what it referred to is an opaque type with some relaxed coercion rules.

What I didn't find out until manually going through my code and cleaning it up is that had I simply run zig fmt it would have known what to change. Nice.

All Together Now

After the above changes Zterm was once again compiling and happy. I've since extended support to FreeBSD, implemented a couple planned features and added a feature that hadn't yet even been considered.

Next time - Gradient backgrounds in Zterm...