diff --git a/README.md b/README.md
index a7fb43e555390f8ed23e40d044c58742fff66373..5edb94b94ec04501f1138cf1cfc2af1c86e1bb09 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ C version (1.x).
 
 ## Requirements
 
-- Zig 0.8 or 0.8.1
+- Zig 0.9.0
 - Some sort of POSIX-like OS
 - ncurses libraries and header files
 
diff --git a/src/browser.zig b/src/browser.zig
index de1ffa79d6710fe4fb4a70e50fe362c44da8e8db..2f3c1c0b2e66bada1d97b97c92859e0125129adf 100644
--- a/src/browser.zig
+++ b/src/browser.zig
@@ -8,7 +8,7 @@ const scan = @import("scan.zig");
 const delete = @import("delete.zig");
 const ui = @import("ui.zig");
 const c = @cImport(@cInclude("time.h"));
-usingnamespace @import("util.zig");
+const util = @import("util.zig");
 
 // Currently opened directory.
 pub var dir_parent: *model.Dir = undefined;
@@ -188,9 +188,9 @@ const Row = struct {
             width += 2 + width;
         defer self.col += width;
         const item = self.item orelse return;
-        const siz = if (main.config.show_blocks) blocksToSize(item.blocks) else item.size;
-        var shr = if (item.dir()) |d| (if (main.config.show_blocks) blocksToSize(d.shared_blocks) else d.shared_size) else 0;
-        if (main.config.show_shared == .unique) shr = saturateSub(siz, shr);
+        const siz = if (main.config.show_blocks) util.blocksToSize(item.blocks) else item.size;
+        var shr = if (item.dir()) |d| (if (main.config.show_blocks) util.blocksToSize(d.shared_blocks) else d.shared_size) else 0;
+        if (main.config.show_shared == .unique) shr = siz -| shr;
 
         ui.move(self.row, self.col);
         ui.addsize(self.bg, siz);
@@ -231,7 +231,7 @@ const Row = struct {
             var siz: u64 = 0;
             self.bg.fg(.graph);
             while (i < bar_size) : (i += 1) {
-                siz = saturateAdd(siz, perblock);
+                siz = siz +| perblock;
                 ui.addch(if (siz <= num) '#' else ' ');
             }
         }
@@ -284,7 +284,7 @@ const Row = struct {
         if (self.item) |i| {
             self.bg.fg(if (i.etype == .dir) .dir else .default);
             ui.addch(if (i.isDirectory()) '/' else ' ');
-            ui.addstr(ui.shorten(ui.toUtf8(i.name()), saturateSub(ui.cols, self.col + 1)));
+            ui.addstr(ui.shorten(ui.toUtf8(i.name()), ui.cols -| self.col +| 1));
         } else {
             self.bg.fg(.dir);
             ui.addstr("/..");
@@ -383,7 +383,7 @@ const info = struct {
     }
 
     fn drawLinks(box: ui.Box, row: *u32, rows: u32, cols: u32) void {
-        const numrows = saturateSub(rows, 4);
+        const numrows = rows -| 4;
         if (links_idx < links_top) links_top = links_idx;
         if (links_idx >= links_top + numrows) links_top = links_idx - numrows + 1;
 
@@ -396,7 +396,7 @@ const info = struct {
             ui.addch(if (&e.entry == entry) '*' else ' ');
             const path = e.path(false);
             defer main.allocator.free(path);
-            ui.addstr(ui.shorten(ui.toUtf8(path), saturateSub(cols, 5)));
+            ui.addstr(ui.shorten(ui.toUtf8(path), cols -| 5));
             row.* += 1;
         }
         ui.style(.default);
@@ -420,7 +420,7 @@ const info = struct {
         if (shared > 0) {
             ui.style(.default);
             drawSizeRow(box, row, "     > shared: ", shared);
-            drawSizeRow(box, row, "     > unique: ", saturateSub(size, shared));
+            drawSizeRow(box, row, "     > unique: ", size -| shared);
         }
     }
 
@@ -466,8 +466,8 @@ const info = struct {
         }
 
         // Disk usage & Apparent size
-        drawSize(box, row, "   Disk usage: ", blocksToSize(e.blocks), if (e.dir()) |d| blocksToSize(d.shared_blocks) else 0);
-        drawSize(box, row, "Apparent size: ", e.size,                 if (e.dir()) |d| d.shared_size                 else 0);
+        drawSize(box, row, "   Disk usage: ", util.blocksToSize(e.blocks), if (e.dir()) |d| util.blocksToSize(d.shared_blocks) else 0);
+        drawSize(box, row, "Apparent size: ", e.size, if (e.dir()) |d| d.shared_size else 0);
 
         // Number of items
         if (e.dir()) |d| {
@@ -552,7 +552,7 @@ const info = struct {
                 set(null, .info);
             }
         }
-        if (keyInputSelection(ch, &cursor_idx, dir_items.items.len, saturateSub(ui.rows, 3))) {
+        if (keyInputSelection(ch, &cursor_idx, dir_items.items.len, ui.rows -| 3)) {
             set(dir_items.items[cursor_idx], .info);
             return true;
         }
@@ -725,10 +725,10 @@ pub fn draw() void {
     ui.style(.hd);
     ui.addstr(" for help");
     if (main.config.imported) {
-        ui.move(0, saturateSub(ui.cols, 10));
+        ui.move(0, ui.cols -| 10);
         ui.addstr("[imported]");
     } else if (!main.config.can_delete.?) {
-        ui.move(0, saturateSub(ui.cols, 10));
+        ui.move(0, ui.cols -| 10);
         ui.addstr("[readonly]");
     }
 
@@ -741,13 +741,13 @@ pub fn draw() void {
 
     var pathbuf = std.ArrayList(u8).init(main.allocator);
     dir_parent.fmtPath(true, &pathbuf);
-    ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5)));
+    ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&pathbuf)), ui.cols -| 5));
     pathbuf.deinit();
 
     ui.style(.default);
     ui.addch(' ');
 
-    const numrows = saturateSub(ui.rows, 3);
+    const numrows = ui.rows -| 3;
     if (cursor_idx < current_view.top) current_view.top = cursor_idx;
     if (cursor_idx >= current_view.top + numrows) current_view.top = cursor_idx - numrows + 1;
 
@@ -770,7 +770,7 @@ pub fn draw() void {
     ui.move(ui.rows-1, 1);
     ui.style(if (main.config.show_blocks) .bold_hd else .hd);
     ui.addstr("Total disk usage: ");
-    ui.addsize(.hd, blocksToSize(dir_parent.entry.blocks));
+    ui.addsize(.hd, util.blocksToSize(dir_parent.entry.blocks));
     ui.style(if (main.config.show_blocks) .hd else .bold_hd);
     ui.addstr("  Apparent size: ");
     ui.addsize(.hd, dir_parent.entry.size);
@@ -810,9 +810,9 @@ fn keyInputSelection(ch: i32, idx: *usize, len: usize, page: u32) bool {
             if (idx.* > 0) idx.* -= 1;
         },
         ui.c.KEY_HOME => idx.* = 0,
-        ui.c.KEY_END, ui.c.KEY_LL => idx.* = saturateSub(len, 1),
-        ui.c.KEY_PPAGE => idx.* = saturateSub(idx.*, page),
-        ui.c.KEY_NPAGE => idx.* = std.math.min(saturateSub(len, 1), idx.* + page),
+        ui.c.KEY_END, ui.c.KEY_LL => idx.* = len -| 1,
+        ui.c.KEY_PPAGE => idx.* = idx.* -| page,
+        ui.c.KEY_NPAGE => idx.* = std.math.min(len -| 1, idx.* + page),
         else => return false,
     }
     return true;
@@ -930,6 +930,6 @@ pub fn keyInput(ch: i32) void {
             .unique => .off,
         },
 
-        else => _ = keyInputSelection(ch, &cursor_idx, dir_items.items.len, saturateSub(ui.rows, 3)),
+        else => _ = keyInputSelection(ch, &cursor_idx, dir_items.items.len, ui.rows -| 3),
     }
 }
diff --git a/src/delete.zig b/src/delete.zig
index b9e68510adad9745242060b9ccbf8b97f3a96b39..663035ffb2566c4870be23963203afb22dd12c90 100644
--- a/src/delete.zig
+++ b/src/delete.zig
@@ -6,7 +6,7 @@ const main = @import("main.zig");
 const model = @import("model.zig");
 const ui = @import("ui.zig");
 const browser = @import("browser.zig");
-usingnamespace @import("util.zig");
+const util = @import("util.zig");
 
 var parent: *model.Dir = undefined;
 var entry: *model.Entry = undefined;
@@ -89,7 +89,7 @@ pub fn delete() ?*model.Entry {
         path.append('/') catch unreachable;
     path.appendSlice(entry.name()) catch unreachable;
 
-    _ = deleteItem(std.fs.cwd(), arrayListBufZ(&path), it);
+    _ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it);
     model.inodes.addAllStats();
     return if (it.* == e) e else next_sel;
 }
@@ -132,7 +132,7 @@ fn drawProgress() void {
 
     const box = ui.Box.create(6, 60, "Deleting...");
     box.move(2, 2);
-    ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&path)), 56));
+    ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 56));
     box.move(4, 41);
     ui.addstr("Press ");
     ui.style(.key);
@@ -151,7 +151,7 @@ fn drawErr() void {
     const box = ui.Box.create(6, 60, "Error");
     box.move(1, 2);
     ui.addstr("Error deleting ");
-    ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&path)), 41));
+    ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 41));
     box.move(2, 4);
     ui.addstr(ui.errorString(error_code));
 
diff --git a/src/main.zig b/src/main.zig
index e7b7786904f7448ccbf2e6ccbdca52f5a9289cef..d98a4fc643647a1e16801bae6d7d0c42ed9695f9 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -16,27 +16,25 @@ const c = @cImport(@cInclude("locale.h"));
 // This allocator never returns an error, it either succeeds or causes ncdu to quit.
 // (Which means you'll find a lot of "catch unreachable" sprinkled through the code,
 // they look scarier than they are)
-fn wrapAlloc(alloc: *std.mem.Allocator, len: usize, alignment: u29, len_align: u29, return_address: usize) error{OutOfMemory}![]u8 {
+fn wrapAlloc(_: *anyopaque, len: usize, alignment: u29, len_align: u29, return_address: usize) error{OutOfMemory}![]u8 {
     while (true) {
-        if (std.heap.c_allocator.allocFn(alloc, len, alignment, len_align, return_address)) |r|
+        if (std.heap.c_allocator.vtable.alloc(undefined, len, alignment, len_align, return_address)) |r|
             return r
         else |_| {}
         ui.oom();
     }
 }
 
-fn wrapResize(alloc: *std.mem.Allocator, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, return_address: usize) std.mem.Allocator.Error!usize {
-    // AFAIK, all uses of resizeFn to grow an allocation will fall back to allocFn on failure.
-    return std.heap.c_allocator.resizeFn(alloc, buf, buf_align, new_len, len_align, return_address);
-}
-
-var allocator_state = std.mem.Allocator{
-    .allocFn = wrapAlloc,
-    .resizeFn = wrapResize,
+pub const allocator = std.mem.Allocator{
+    .ptr = undefined,
+    .vtable = &.{
+        .alloc = wrapAlloc,
+        // AFAIK, all uses of resize() to grow an allocation will fall back to alloc() on failure.
+        .resize = std.heap.c_allocator.vtable.resize,
+        .free = std.heap.c_allocator.vtable.free,
+    },
 };
-pub const allocator = &allocator_state;
-//var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
-//pub const allocator = &general_purpose_allocator.allocator;
+
 
 pub const config = struct {
     pub const SortCol = enum { name, blocks, size, items, mtime };
@@ -388,7 +386,7 @@ pub fn main() void {
     _ = c.setlocale(c.LC_ALL, "");
     if (c.localeconv()) |locale| {
         if (locale.*.thousands_sep) |sep| {
-            const span = std.mem.spanZ(sep);
+            const span = std.mem.sliceTo(sep, 0);
             if (span.len > 0)
                 config.thousands_sep = span;
         }
@@ -444,7 +442,7 @@ pub fn main() void {
         }
     }
 
-    if (std.builtin.os.tag != .linux and config.exclude_kernfs)
+    if (@import("builtin").os.tag != .linux and config.exclude_kernfs)
         ui.die("The --exclude-kernfs tag is currently only supported on Linux.\n", .{});
 
     const out_tty = std.io.getStdOut().isTty();
diff --git a/src/model.zig b/src/model.zig
index 5d611ba6fbe4e8c1e94aa3ea7082aa3cc406cf56..1839b7e57f44fbddf6a8cd5170d220f4d471ffbf 100644
--- a/src/model.zig
+++ b/src/model.zig
@@ -4,7 +4,7 @@
 const std = @import("std");
 const main = @import("main.zig");
 const ui = @import("ui.zig");
-usingnamespace @import("util.zig");
+const util = @import("util.zig");
 
 // While an arena allocator is optimimal for almost all scenarios in which ncdu
 // is used, it doesn't allow for re-using deleted nodes after doing a delete or
@@ -12,9 +12,10 @@ usingnamespace @import("util.zig");
 // will leak memory, but I'd say that's worth the efficiency gains.
 // TODO: Can still implement a simple bucketed free list on top of this arena
 // allocator to reuse nodes, if necessary.
-var allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+var allocator_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+const allocator = allocator_state.allocator();
 
-pub const EType = packed enum(u2) { dir, link, file };
+pub const EType = enum(u2) { dir, link, file };
 
 // Type for the Entry.blocks field. Smaller than a u64 to make room for flags.
 pub const Blocks = u60;
@@ -66,15 +67,15 @@ pub const Entry = packed struct {
 
     fn nameOffset(etype: EType) usize {
         return switch (etype) {
-            .dir => @byteOffsetOf(Dir, "name"),
-            .link => @byteOffsetOf(Link, "name"),
-            .file => @byteOffsetOf(File, "name"),
+            .dir => @offsetOf(Dir, "name"),
+            .link => @offsetOf(Link, "name"),
+            .file => @offsetOf(File, "name"),
         };
     }
 
     pub fn name(self: *const Self) [:0]const u8 {
         const ptr = @ptrCast([*:0]const u8, self) + nameOffset(self.etype);
-        return ptr[0..std.mem.lenZ(ptr) :0];
+        return std.mem.sliceTo(ptr, 0);
     }
 
     pub fn ext(self: *Self) ?*Ext {
@@ -87,7 +88,7 @@ pub const Entry = packed struct {
         const size = nameOffset(etype) + ename.len + 1 + extsize;
         var ptr = blk: {
             while (true) {
-                if (allocator.allocator.allocWithOptions(u8, size, std.math.max(@alignOf(Ext), @alignOf(Entry)), null)) |p|
+                if (allocator.allocWithOptions(u8, size, std.math.max(@alignOf(Ext), @alignOf(Entry)), null)) |p|
                     break :blk p
                 else |_| {}
                 ui.oom();
@@ -124,7 +125,7 @@ pub const Entry = packed struct {
             var d = inodes.map.getOrPut(l) catch unreachable;
             if (!d.found_existing) {
                 d.value_ptr.* = .{ .counted = false, .nlink = nlink };
-                inodes.total_blocks = saturateAdd(inodes.total_blocks, self.blocks);
+                inodes.total_blocks +|= self.blocks;
                 l.next = l;
             } else {
                 inodes.setStats(.{ .key_ptr = d.key_ptr, .value_ptr = d.value_ptr }, false);
@@ -142,10 +143,10 @@ pub const Entry = packed struct {
             if (self.ext()) |e|
                 if (p.entry.ext()) |pe|
                     if (e.mtime > pe.mtime) { pe.mtime = e.mtime; };
-            p.items = saturateAdd(p.items, 1);
+            p.items +|= 1;
             if (self.etype != .link) {
-                p.entry.size = saturateAdd(p.entry.size, self.size);
-                p.entry.blocks = saturateAdd(p.entry.blocks, self.blocks);
+                p.entry.size +|= self.size;
+                p.entry.blocks +|= self.blocks;
             }
         }
     }
@@ -174,7 +175,7 @@ pub const Entry = packed struct {
             if (l.next == l) {
                 _ = inodes.map.remove(l);
                 _ = inodes.uncounted.remove(l);
-                inodes.total_blocks = saturateSub(inodes.total_blocks, self.blocks);
+                inodes.total_blocks -|= self.blocks;
             } else {
                 if (d.key_ptr.* == l)
                     d.key_ptr.* = l.next;
@@ -193,10 +194,10 @@ pub const Entry = packed struct {
 
         var it: ?*Dir = parent;
         while(it) |p| : (it = p.parent) {
-            p.items = saturateSub(p.items, 1);
+            p.items -|= 1;
             if (self.etype != .link) {
-                p.entry.size = saturateSub(p.entry.size, self.size);
-                p.entry.blocks = saturateSub(p.entry.blocks, self.blocks);
+                p.entry.size -|= self.size;
+                p.entry.blocks -|= self.blocks;
             }
         }
     }
@@ -234,7 +235,7 @@ pub const Dir = packed struct {
     err: bool,
     suberr: bool,
 
-    // Only used to find the @byteOffsetOff, the name is written at this point as a 0-terminated string.
+    // Only used to find the @offsetOff, the name is written at this point as a 0-terminated string.
     // (Old C habits die hard)
     name: u8,
 
@@ -418,20 +419,20 @@ pub const inodes = struct {
         var dir_iter = dirs.iterator();
         if (add) {
             while (dir_iter.next()) |de| {
-                de.key_ptr.*.entry.blocks = saturateAdd(de.key_ptr.*.entry.blocks, entry.key_ptr.*.entry.blocks);
-                de.key_ptr.*.entry.size   = saturateAdd(de.key_ptr.*.entry.size,   entry.key_ptr.*.entry.size);
+                de.key_ptr.*.entry.blocks +|= entry.key_ptr.*.entry.blocks;
+                de.key_ptr.*.entry.size   +|= entry.key_ptr.*.entry.size;
                 if (de.value_ptr.* < nlink) {
-                    de.key_ptr.*.shared_blocks = saturateAdd(de.key_ptr.*.shared_blocks, entry.key_ptr.*.entry.blocks);
-                    de.key_ptr.*.shared_size   = saturateAdd(de.key_ptr.*.shared_size,   entry.key_ptr.*.entry.size);
+                    de.key_ptr.*.shared_blocks +|= entry.key_ptr.*.entry.blocks;
+                    de.key_ptr.*.shared_size   +|= entry.key_ptr.*.entry.size;
                 }
             }
         } else {
             while (dir_iter.next()) |de| {
-                de.key_ptr.*.entry.blocks = saturateSub(de.key_ptr.*.entry.blocks, entry.key_ptr.*.entry.blocks);
-                de.key_ptr.*.entry.size   = saturateSub(de.key_ptr.*.entry.size,   entry.key_ptr.*.entry.size);
+                de.key_ptr.*.entry.blocks -|= entry.key_ptr.*.entry.blocks;
+                de.key_ptr.*.entry.size   -|= entry.key_ptr.*.entry.size;
                 if (de.value_ptr.* < nlink) {
-                    de.key_ptr.*.shared_blocks = saturateSub(de.key_ptr.*.shared_blocks, entry.key_ptr.*.entry.blocks);
-                    de.key_ptr.*.shared_size   = saturateSub(de.key_ptr.*.shared_size,   entry.key_ptr.*.entry.size);
+                    de.key_ptr.*.shared_blocks -|= entry.key_ptr.*.entry.blocks;
+                    de.key_ptr.*.shared_size   -|= entry.key_ptr.*.entry.size;
                 }
             }
         }
diff --git a/src/scan.zig b/src/scan.zig
index ed92a353b74db5457ff6507beff87a235682861a..01b01a0a6d0a66f9e022b4afb217358334d388ba 100644
--- a/src/scan.zig
+++ b/src/scan.zig
@@ -5,7 +5,7 @@ const std = @import("std");
 const main = @import("main.zig");
 const model = @import("model.zig");
 const ui = @import("ui.zig");
-usingnamespace @import("util.zig");
+const util = @import("util.zig");
 const c_statfs = @cImport(@cInclude("sys/vfs.h"));
 const c_fnmatch = @cImport(@cInclude("fnmatch.h"));
 
@@ -24,25 +24,25 @@ const Stat = struct {
     ext: model.Ext = .{},
 
     fn clamp(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).field_type {
-        return castClamp(std.meta.fieldInfo(T, field).field_type, x);
+        return util.castClamp(std.meta.fieldInfo(T, field).field_type, x);
     }
 
     fn truncate(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).field_type {
-        return castTruncate(std.meta.fieldInfo(T, field).field_type, x);
+        return util.castTruncate(std.meta.fieldInfo(T, field).field_type, x);
     }
 
     fn read(parent: std.fs.Dir, name: [:0]const u8, follow: bool) !Stat {
-        const stat = try std.os.fstatatZ(parent.fd, name, if (follow) 0 else std.os.AT_SYMLINK_NOFOLLOW);
+        const stat = try std.os.fstatatZ(parent.fd, name, if (follow) 0 else std.os.AT.SYMLINK_NOFOLLOW);
         return Stat{
             .blocks = clamp(Stat, .blocks, stat.blocks),
             .size = clamp(Stat, .size, stat.size),
             .dev = truncate(Stat, .dev, stat.dev),
             .ino = truncate(Stat, .ino, stat.ino),
             .nlink = clamp(Stat, .nlink, stat.nlink),
-            .hlinkc = stat.nlink > 1 and !std.os.system.S_ISDIR(stat.mode),
-            .dir = std.os.system.S_ISDIR(stat.mode),
-            .reg = std.os.system.S_ISREG(stat.mode),
-            .symlink = std.os.system.S_ISLNK(stat.mode),
+            .hlinkc = stat.nlink > 1 and !std.os.system.S.ISDIR(stat.mode),
+            .dir = std.os.system.S.ISDIR(stat.mode),
+            .reg = std.os.system.S.ISREG(stat.mode),
+            .symlink = std.os.system.S.ISLNK(stat.mode),
             .ext = .{
                 .mtime = clamp(model.Ext, .mtime, stat.mtime().tv_sec),
                 .uid = truncate(model.Ext, .uid, stat.uid),
@@ -141,7 +141,7 @@ const ScanDir = struct {
         var count: Map.Size = 0;
         var it = dir.sub;
         while (it) |e| : (it = e.next) count += 1;
-        self.entries.ensureCapacity(count) catch unreachable;
+        self.entries.ensureUnusedCapacity(count) catch unreachable;
 
         it = dir.sub;
         while (it) |e| : (it = e.next)
@@ -345,7 +345,7 @@ const Context = struct {
     }
 
     fn pathZ(self: *Self) [:0]const u8 {
-        return arrayListBufZ(&self.path);
+        return util.arrayListBufZ(&self.path);
     }
 
     // Set a flag to indicate that there was an error listing file entries in the current directory.
@@ -396,7 +396,7 @@ const Context = struct {
         try w.writeAll("{\"name\":");
         try writeJsonString(w, self.name);
         if (self.stat.size > 0) try w.print(",\"asize\":{d}", .{ self.stat.size });
-        if (self.stat.blocks > 0) try w.print(",\"dsize\":{d}", .{ blocksToSize(self.stat.blocks) });
+        if (self.stat.blocks > 0) try w.print(",\"dsize\":{d}", .{ util.blocksToSize(self.stat.blocks) });
         if (self.stat.dir and self.stat.dev != dir_dev) try w.print(",\"dev\":{d}", .{ self.stat.dev });
         if (self.stat.hlinkc) try w.print(",\"ino\":{d},\"hlnkc\":true,\"nlink\":{d}", .{ self.stat.ino, self.stat.nlink });
         if (!self.stat.dir and !self.stat.reg) try w.writeAll(",\"notreg\":true");
@@ -508,7 +508,7 @@ fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) void {
             } else null;
         defer if (edir != null) edir.?.close();
 
-        if (std.builtin.os.tag == .linux and main.config.exclude_kernfs and ctx.stat.dir and isKernfs(edir.?, ctx.stat.dev)) {
+        if (@import("builtin").os.tag == .linux and main.config.exclude_kernfs and ctx.stat.dir and isKernfs(edir.?, ctx.stat.dev)) {
             ctx.addSpecial(.kernfs);
             continue;
         }
@@ -980,18 +980,18 @@ var animation_pos: u32 = 0;
 var need_confirm_quit = false;
 
 fn drawError(err: anyerror) void {
-    const width = saturateSub(ui.cols, 5);
+    const width = ui.cols -| 5;
     const box = ui.Box.create(7, width, "Scan error");
 
     box.move(2, 2);
     ui.addstr("Path: ");
-    ui.addstr(ui.shorten(ui.toUtf8(active_context.last_error.?), saturateSub(width, 10)));
+    ui.addstr(ui.shorten(ui.toUtf8(active_context.last_error.?), width -| 10));
 
     box.move(3, 2);
     ui.addstr("Error: ");
-    ui.addstr(ui.shorten(ui.errorString(err), saturateSub(width, 6)));
+    ui.addstr(ui.shorten(ui.errorString(err), width -| 6));
 
-    box.move(5, saturateSub(width, 27));
+    box.move(5, width -| 27);
     ui.addstr("Press any key to continue");
 }
 
@@ -999,7 +999,7 @@ fn drawBox() void {
     ui.init();
     const ctx = active_context;
     if (ctx.fatal_error) |err| return drawError(err);
-    const width = saturateSub(ui.cols, 5);
+    const width = ui.cols -| 5;
     const box = ui.Box.create(10, width, "Scanning...");
     box.move(2, 2);
     ui.addstr("Total items: ");
@@ -1009,12 +1009,12 @@ fn drawBox() void {
         box.move(2, 30);
         ui.addstr("size: ");
         // TODO: Should display the size of the dir-to-be-refreshed on refreshing, not the root.
-        ui.addsize(.default, blocksToSize(saturateAdd(model.root.entry.blocks, model.inodes.total_blocks)));
+        ui.addsize(.default, util.blocksToSize(model.root.entry.blocks +| model.inodes.total_blocks));
     }
 
     box.move(3, 2);
     ui.addstr("Current item: ");
-    ui.addstr(ui.shorten(ui.toUtf8(ctx.pathZ()), saturateSub(width, 18)));
+    ui.addstr(ui.shorten(ui.toUtf8(ctx.pathZ()), width -| 18));
 
     if (ctx.last_error) |path| {
         box.move(5, 2);
@@ -1022,20 +1022,20 @@ fn drawBox() void {
         ui.addstr("Warning: ");
         ui.style(.default);
         ui.addstr("error scanning ");
-        ui.addstr(ui.shorten(ui.toUtf8(path), saturateSub(width, 28)));
+        ui.addstr(ui.shorten(ui.toUtf8(path), width -| 28));
         box.move(6, 3);
         ui.addstr("some directory sizes may not be correct.");
     }
 
     if (need_confirm_quit) {
-        box.move(8, saturateSub(width, 20));
+        box.move(8, width -| 20);
         ui.addstr("Press ");
         ui.style(.key);
         ui.addch('y');
         ui.style(.default);
         ui.addstr(" to confirm");
     } else {
-        box.move(8, saturateSub(width, 18));
+        box.move(8, width -| 18);
         ui.addstr("Press ");
         ui.style(.key);
         ui.addch('q');
@@ -1074,7 +1074,7 @@ pub fn draw() void {
                     .{ ui.shorten(active_context.pathZ(), 63), active_context.items_seen }
                 ) catch return;
             } else {
-                const r = ui.FmtSize.fmt(blocksToSize(model.root.entry.blocks));
+                const r = ui.FmtSize.fmt(util.blocksToSize(model.root.entry.blocks));
                 line = std.fmt.bufPrint(&buf, "\x1b7\x1b[J{s: <51} {d:>9} files / {s}{s}\x1b8",
                     .{ ui.shorten(active_context.pathZ(), 51), active_context.items_seen, r.num(), r.unit }
                 ) catch return;
diff --git a/src/ui.zig b/src/ui.zig
index 08fbdea865a0e7d2c345fc1ed8a8f6c14429efd2..c2cfb5c29448383c4ab4af68c0923a632408e49e 100644
--- a/src/ui.zig
+++ b/src/ui.zig
@@ -5,7 +5,7 @@
 
 const std = @import("std");
 const main = @import("main.zig");
-usingnamespace @import("util.zig");
+const util = @import("util.zig");
 
 pub const c = @cImport({
     @cInclude("stdio.h");
@@ -73,7 +73,7 @@ pub fn errorString(e: anyerror) [:0]const u8 {
         error.ReadOnlyFilesystem => "Read-only filesystem",
         error.SymlinkLoop => "Symlink loop",
         error.SystemFdQuotaExceeded => "System file descriptor limit exceeded",
-        else => @bitCast([:0]const u8, @errorName(e)), // XXX: The bitCast can be removed after a Zig >0.8 release.
+        else => @errorName(e),
     };
 }
 
@@ -113,7 +113,7 @@ pub fn toUtf8(in: [:0]const u8) [:0]const u8 {
         to_utf8_buf.writer().print("\\x{X:0>2}", .{in[i]}) catch unreachable;
         i += 1;
     }
-    return arrayListBufZ(&to_utf8_buf);
+    return util.arrayListBufZ(&to_utf8_buf);
 }
 
 var shorten_buf = std.ArrayList(u8).init(main.allocator);
@@ -163,7 +163,7 @@ pub fn shorten(in: [:0]const u8, max_width: u32) [:0] const u8 {
             break;
         }
     }
-    return arrayListBufZ(&shorten_buf);
+    return util.arrayListBufZ(&shorten_buf);
 }
 
 fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) !void {
@@ -348,7 +348,7 @@ pub fn init() void {
     clearScr();
     if (main.config.nc_tty) {
         var tty = c.fopen("/dev/tty", "r+");
-        if (tty == null) die("Error opening /dev/tty: {s}.\n", .{ c.strerror(std.c.getErrno(-1)) });
+        if (tty == null) die("Error opening /dev/tty: {s}.\n", .{ c.strerror(@enumToInt(std.c.getErrno(-1))) });
         var term = c.newterm(null, tty, tty);
         if (term == null) die("Error initializing ncurses.\n", .{});
         _ = c.set_term(term);
@@ -439,7 +439,7 @@ pub const FmtSize = struct {
     }
 
     pub fn num(self: *const @This()) [:0]const u8 {
-        return std.mem.spanZ(&self.buf);
+        return std.mem.sliceTo(&self.buf, 0);
     }
 };
 
@@ -478,14 +478,14 @@ pub fn addnum(bg: Bg, v: u64) void {
 
 // Print a file mode, takes 10 columns
 pub fn addmode(mode: u32) void {
-    addch(switch (mode & std.os.S_IFMT) {
-        std.os.S_IFDIR  => 'd',
-        std.os.S_IFREG  => '-',
-        std.os.S_IFLNK  => 'l',
-        std.os.S_IFIFO  => 'p',
-        std.os.S_IFSOCK => 's',
-        std.os.S_IFCHR  => 'c',
-        std.os.S_IFBLK  => 'b',
+    addch(switch (mode & std.os.S.IFMT) {
+        std.os.S.IFDIR  => 'd',
+        std.os.S.IFREG  => '-',
+        std.os.S.IFLNK  => 'l',
+        std.os.S.IFIFO  => 'p',
+        std.os.S.IFSOCK => 's',
+        std.os.S.IFCHR  => 'c',
+        std.os.S.IFBLK  => 'b',
         else => '?'
     });
     addch(if (mode &  0o400 > 0) 'r' else '-');
@@ -496,12 +496,12 @@ pub fn addmode(mode: u32) void {
     addch(if (mode & 0o2000 > 0) 's' else if (mode & 0o010 > 0) @as(u7, 'x') else '-');
     addch(if (mode &  0o004 > 0) 'r' else '-');
     addch(if (mode &  0o002 > 0) 'w' else '-');
-    addch(if (mode & 0o1000 > 0) (if (std.os.S_ISDIR(mode)) @as(u7, 't') else 'T') else if (mode & 0o001 > 0) @as(u7, 'x') else '-');
+    addch(if (mode & 0o1000 > 0) (if (std.os.S.ISDIR(mode)) @as(u7, 't') else 'T') else if (mode & 0o001 > 0) @as(u7, 'x') else '-');
 }
 
 // Print a timestamp, takes 25 columns
 pub fn addts(bg: Bg, ts: u64) void {
-    const t = castClamp(c.time_t, ts);
+    const t = util.castClamp(c.time_t, ts);
     var buf: [32:0]u8 = undefined;
     const len = c.strftime(&buf, buf.len, "%Y-%m-%d %H:%M:%S %z", c.localtime(&t));
     if (len > 0) {
@@ -526,8 +526,8 @@ pub const Box = struct {
 
     pub fn create(height: u32, width: u32, title: [:0]const u8) Self {
         const s = Self{
-            .start_row = saturateSub(rows>>1, height>>1),
-            .start_col = saturateSub(cols>>1, width>>1),
+            .start_row = (rows>>1) -| (height>>1),
+            .start_col = (cols>>1) -| (width>>1),
         };
         style(.default);
         if (width < 6 or height < 3) return s;
@@ -597,5 +597,5 @@ pub fn getch(block: bool) i32 {
         return ch;
     }
     die("Error reading keyboard input, assuming TTY has been lost.\n(Potentially nonsensical error message: {s})\n",
-        .{ c.strerror(std.c.getErrno(-1)) });
+        .{ c.strerror(@enumToInt(std.c.getErrno(-1))) });
 }
diff --git a/src/util.zig b/src/util.zig
index 4c5c8a930adc8130581b572e64fa026b02796b99..e6191af8a8bff75212de64cd18ebaffbf528bd74 100644
--- a/src/util.zig
+++ b/src/util.zig
@@ -3,16 +3,6 @@
 
 const std = @import("std");
 
-pub fn saturateAdd(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
-    std.debug.assert(@typeInfo(@TypeOf(a)).Int.signedness == .unsigned);
-    return std.math.add(@TypeOf(a), a, b) catch std.math.maxInt(@TypeOf(a));
-}
-
-pub fn saturateSub(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
-    std.debug.assert(@typeInfo(@TypeOf(a)).Int.signedness == .unsigned);
-    return std.math.sub(@TypeOf(a), a, b) catch std.math.minInt(@TypeOf(a));
-}
-
 // Cast any integer type to the target type, clamping the value to the supported maximum if necessary.
 pub fn castClamp(comptime T: type, x: anytype) T {
     // (adapted from std.math.cast)