diff --git a/README.md b/README.md index 29a917bdd528fc91140d29c03e3c3f6eeeb54f11..d5dbe392ad738ac48588a7e1eaa7b94755ba0835 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ Missing features: - Directory refresh - File deletion - Opening a shell -- OOM handling ### Improvements compared to the C version diff --git a/src/browser.zig b/src/browser.zig index 042537e9423385e557eeeea486a32751f9954dbb..7ed05f734919caec132c8bc5a7d3882609fdfff1 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -120,24 +120,24 @@ fn sortDir() void { // - dir_parents changes (i.e. we change directory) // - config.show_hidden changes // - files in this dir have been added or removed -pub fn loadDir() !void { +pub fn loadDir() void { dir_items.shrinkRetainingCapacity(0); dir_max_size = 1; dir_max_blocks = 1; if (dir_parents.top() != model.root) - try dir_items.append(null); + dir_items.append(null) catch unreachable; var it = dir_parents.top().sub; while (it) |e| { if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks; if (e.size > dir_max_size) dir_max_size = e.size; if (main.config.show_hidden) // fast path - try dir_items.append(e) + dir_items.append(e) catch unreachable else { const excl = if (e.file()) |f| f.excluded else false; const name = e.name(); if (!excl and name[0] != '.' and name[name.len-1] != '~') - try dir_items.append(e); + dir_items.append(e) catch unreachable; } it = e.next; } @@ -271,19 +271,19 @@ const Row = struct { ui.addstr(" no mtime"); } - fn name(self: *Self) !void { + fn name(self: *Self) void { ui.move(self.row, self.col); if (self.item) |i| { self.bg.fg(if (i.etype == .dir) .dir else .default); ui.addch(if (i.etype == .dir) '/' else ' '); - ui.addstr(try ui.shorten(try ui.toUtf8(i.name()), saturateSub(ui.cols, self.col + 1))); + ui.addstr(ui.shorten(ui.toUtf8(i.name()), saturateSub(ui.cols, self.col + 1))); } else { self.bg.fg(.dir); ui.addstr("/.."); } } - fn draw(self: *Self) !void { + fn draw(self: *Self) void { if (self.bg == .sel) { self.bg.fg(.default); ui.move(self.row, 0); @@ -294,7 +294,7 @@ const Row = struct { self.graph(); self.items(); self.mtime(); - try self.name(); + self.name(); } }; @@ -314,7 +314,7 @@ fn drawQuit() void { ui.addch(')'); } -pub fn draw() !void { +pub fn draw() void { ui.style(.hd); ui.move(0,0); ui.hline(' ', ui.cols); @@ -340,8 +340,8 @@ pub fn draw() !void { ui.style(.dir); var pathbuf = std.ArrayList(u8).init(main.allocator); - try dir_parents.path(pathbuf.writer()); - ui.addstr(try ui.shorten(try ui.toUtf8(try arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5))); + dir_parents.path(&pathbuf); + ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5))); pathbuf.deinit(); ui.style(.default); @@ -361,7 +361,7 @@ pub fn draw() !void { .bg = if (i+current_view.top == cursor_idx) .sel else .default, }; if (row.bg == .sel) sel_row = i+2; - try row.draw(); + row.draw(); } ui.style(.hd); @@ -389,7 +389,7 @@ fn sortToggle(col: main.config.SortCol, default_order: main.config.SortOrder) vo sortDir(); } -pub fn keyInput(ch: i32) !void { +pub fn keyInput(ch: i32) void { if (need_confirm_quit) { switch (ch) { 'y', 'Y' => if (need_confirm_quit) ui.quit(), @@ -422,7 +422,7 @@ pub fn keyInput(ch: i32) !void { 'M' => if (main.config.extended) sortToggle(.mtime, .desc), 'e' => { main.config.show_hidden = !main.config.show_hidden; - try loadDir(); + loadDir(); }, 't' => { main.config.sort_dirsfirst = !main.config.sort_dirsfirst; @@ -445,18 +445,18 @@ pub fn keyInput(ch: i32) !void { if (dir_items.items.len == 0) { } else if (dir_items.items[cursor_idx]) |e| { if (e.dir()) |d| { - try dir_parents.push(d); - try loadDir(); + dir_parents.push(d); + loadDir(); } } else if (dir_parents.top() != model.root) { dir_parents.pop(); - try loadDir(); + loadDir(); } }, 'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => { if (dir_parents.top() != model.root) { dir_parents.pop(); - try loadDir(); + loadDir(); } }, diff --git a/src/main.zig b/src/main.zig index e0bdba643dfae28ab943cf7d530ebae7a4de6aa8..195e3d9a1a0f03a8c24f0a3878e50db01dc8aeef 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7,7 +7,29 @@ const ui = @import("ui.zig"); const browser = @import("browser.zig"); const c = @cImport(@cInclude("locale.h")); -pub const allocator = std.heap.c_allocator; +// "Custom" allocator that wraps the libc allocator and calls ui.oom() on error. +// 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 { + while (true) { + if (std.heap.c_allocator.allocFn(alloc, 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 = &allocator_state; pub const config = struct { pub const SortCol = enum { name, blocks, size, items, mtime }; @@ -158,7 +180,7 @@ fn readExcludeFile(path: []const u8) !void { rd.readUntilDelimiterArrayList(&buf, '\n', 4096) catch |e| if (e != error.EndOfStream) return e else if (buf.items.len == 0) break; if (buf.items.len > 0) - try config.exclude_patterns.append(try buf.toOwnedSliceSentinel(0)); + config.exclude_patterns.append(buf.toOwnedSliceSentinel(0) catch unreachable) catch unreachable; } } @@ -203,7 +225,7 @@ pub fn main() !void { else if(opt.is("-f")) import_file = args.arg() else if(opt.is("--si")) config.si = true else if(opt.is("-L") or opt.is("--follow-symlinks")) config.follow_symlinks = true - else if(opt.is("--exclude")) try config.exclude_patterns.append(args.arg()) + else if(opt.is("--exclude")) config.exclude_patterns.append(args.arg()) catch unreachable else if(opt.is("-X") or opt.is("--exclude-from")) { const arg = args.arg(); readExcludeFile(arg) catch |e| ui.die("Error reading excludes from {s}: {}.\n", .{ arg, e }); @@ -249,22 +271,21 @@ pub fn main() !void { config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode. ui.init(); state = .browse; - try browser.loadDir(); + browser.loadDir(); - // TODO: Handle OOM errors - while (true) try handleEvent(true, false); + while (true) handleEvent(true, false); } var event_delay_timer: std.time.Timer = undefined; // Draw the screen and handle the next input event. // In non-blocking mode, screen drawing is rate-limited to keep this function fast. -pub fn handleEvent(block: bool, force_draw: bool) !void { +pub fn handleEvent(block: bool, force_draw: bool) void { if (block or force_draw or event_delay_timer.read() > config.update_delay) { if (ui.inited) _ = ui.c.erase(); switch (state) { - .scan => try scan.draw(), - .browse => try browser.draw(), + .scan => scan.draw(), + .browse => browser.draw(), } if (ui.inited) _ = ui.c.refresh(); event_delay_timer.reset(); @@ -280,8 +301,8 @@ pub fn handleEvent(block: bool, force_draw: bool) !void { if (ch == 0) return; if (ch == -1) return handleEvent(firstblock, true); switch (state) { - .scan => try scan.keyInput(ch), - .browse => try browser.keyInput(ch), + .scan => scan.keyInput(ch), + .browse => browser.keyInput(ch), } firstblock = false; } diff --git a/src/model.zig b/src/model.zig index 760300eff495d5d8d8703340f418d49c25fce50a..842e2204e209ba42faa0d946c945a0ef7ba61d16 100644 --- a/src/model.zig +++ b/src/model.zig @@ -1,5 +1,6 @@ const std = @import("std"); const main = @import("main.zig"); +const ui = @import("ui.zig"); usingnamespace @import("util.zig"); // While an arena allocator is optimimal for almost all scenarios in which ncdu @@ -67,17 +68,23 @@ pub const Entry = packed struct { return @intToPtr(*Ext, std.mem.alignForward(@ptrToInt(self) + nameOffset(self.etype) + n.len + 1, @alignOf(Ext))); } - pub fn create(etype: EType, isext: bool, ename: []const u8) !*Entry { + pub fn create(etype: EType, isext: bool, ename: []const u8) *Entry { const base_size = nameOffset(etype) + ename.len + 1; const size = (if (isext) std.mem.alignForward(base_size, @alignOf(Ext))+@sizeOf(Ext) else base_size); - var ptr = try allocator.allocator.allocWithOptions(u8, size, @alignOf(Entry), null); + var ptr = blk: { + while (true) { + if (allocator.allocator.allocWithOptions(u8, size, @alignOf(Entry), null)) |p| + break :blk p + else |_| {} + ui.oom(); + } + }; std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick var e = @ptrCast(*Entry, ptr); e.etype = etype; e.isext = isext; var name_ptr = @intToPtr([*]u8, @ptrToInt(e) + nameOffset(etype)); std.mem.copy(u8, name_ptr[0..ename.len], ename); - //std.debug.warn("{any}\n", .{ @ptrCast([*]u8, e)[0..size] }); return e; } @@ -95,8 +102,7 @@ pub const Entry = packed struct { } // Insert this entry into the tree at the given directory, updating parent sizes and item counts. - // (TODO: This function creates an unrecoverable mess on OOM, need to do something better) - pub fn insert(self: *Entry, parents: *const Parents) !void { + pub fn insert(self: *Entry, parents: *const Parents) void { self.next = parents.top().sub; parents.top().sub = self; if (self.dir()) |d| std.debug.assert(d.sub == null); @@ -121,7 +127,7 @@ pub const Entry = packed struct { } else if (self.link()) |l| { const n = HardlinkNode{ .ino = l.ino, .dir = p, .num_files = 1 }; - var d = try devices.items[dev].hardlinks.getOrPut(n); + var d = devices.items[dev].hardlinks.getOrPut(n) catch unreachable; new_hl = !d.found_existing; if (d.found_existing) d.entry.key.num_files += 1; // First time we encounter this file in this dir, count it. @@ -285,12 +291,12 @@ const Device = struct { var devices: std.ArrayList(Device) = std.ArrayList(Device).init(main.allocator); var dev_lookup: std.AutoHashMap(u64, DevId) = std.AutoHashMap(u64, DevId).init(main.allocator); -pub fn getDevId(dev: u64) !DevId { - var d = try dev_lookup.getOrPut(dev); +pub fn getDevId(dev: u64) DevId { + var d = dev_lookup.getOrPut(dev) catch unreachable; if (!d.found_existing) { errdefer dev_lookup.removeAssertDiscard(dev); d.entry.value = @intCast(DevId, devices.items.len); - try devices.append(.{ .dev = dev }); + devices.append(.{ .dev = dev }) catch unreachable; } return d.entry.value; } @@ -308,8 +314,8 @@ pub const Parents = struct { const Self = @This(); - pub fn push(self: *Self, dir: *Dir) !void { - return self.stack.append(dir); + pub fn push(self: *Self, dir: *Dir) void { + return self.stack.append(dir) catch unreachable; } // Attempting to remove the root node is considered a bug. @@ -338,13 +344,14 @@ pub const Parents = struct { return .{ .lst = self }; } - pub fn path(self: *const Self, wr: anytype) !void { + // Append the path to the given arraylist. The list is assumed to use main.allocator, so it can't fail. + pub fn path(self: *const Self, out: *std.ArrayList(u8)) void { const r = root.entry.name(); - try wr.writeAll(r); + out.appendSlice(r) catch unreachable; var i: usize = 0; while (i < self.stack.items.len) { - if (i != 0 or r[r.len-1] != '/') try wr.writeByte('/'); - try wr.writeAll(self.stack.items[i].entry.name()); + if (i != 0 or r[r.len-1] != '/') out.append('/') catch unreachable; + out.appendSlice(self.stack.items[i].entry.name()) catch unreachable; i += 1; } } diff --git a/src/scan.zig b/src/scan.zig index b90b8479e9000b0c7be2a13d27950f5ef951d3d8..6323ce5d67bec1987944067a57cc949491bba80c 100644 --- a/src/scan.zig +++ b/src/scan.zig @@ -132,7 +132,7 @@ const Context = struct { const Self = @This(); fn initFile(out: std.fs.File) !Self { - var buf = try main.allocator.create(Writer); + var buf = main.allocator.create(Writer) catch unreachable; errdefer main.allocator.destroy(buf); buf.* = std.io.bufferedWriter(out.writer()); var wr = buf.writer(); @@ -154,13 +154,13 @@ const Context = struct { } // Add the name of the file/dir entry we're currently inspecting - fn pushPath(self: *Self, name: []const u8) !void { - try self.path_indices.append(self.path.items.len); - if (self.path.items.len > 1) try self.path.append('/'); + fn pushPath(self: *Self, name: []const u8) void { + self.path_indices.append(self.path.items.len) catch unreachable; + if (self.path.items.len > 1) self.path.append('/') catch unreachable; const start = self.path.items.len; - try self.path.appendSlice(name); + self.path.appendSlice(name) catch unreachable; - try self.path.append(0); + self.path.append(0) catch unreachable; self.name = self.path.items[start..self.path.items.len-1:0]; self.path.items.len -= 1; } @@ -177,7 +177,7 @@ const Context = struct { } fn pathZ(self: *Self) [:0]const u8 { - return arrayListBufZ(&self.path) catch unreachable; + return arrayListBufZ(&self.path); } // Set a flag to indicate that there was an error listing file entries in the current directory. @@ -195,12 +195,12 @@ const Context = struct { if (t == .err) { if (self.last_error) |p| main.allocator.free(p); - self.last_error = try main.allocator.dupeZ(u8, self.path.items); + self.last_error = main.allocator.dupeZ(u8, self.path.items) catch unreachable; } if (self.parents) |*p| { - var e = try model.Entry.create(.file, false, self.name); - e.insert(p) catch unreachable; + var e = model.Entry.create(.file, false, self.name); + e.insert(p); var f = e.file().?; switch (t) { .err => e.set_err(p), @@ -233,10 +233,10 @@ const Context = struct { const etype = if (self.stat.dir) model.EType.dir else if (self.stat.hlinkc) model.EType.link else model.EType.file; - var e = try model.Entry.create(etype, main.config.extended, self.name); + var e = model.Entry.create(etype, main.config.extended, self.name); e.blocks = self.stat.blocks; e.size = self.stat.size; - if (e.dir()) |d| d.dev = try model.getDevId(self.stat.dev); + if (e.dir()) |d| d.dev = model.getDevId(self.stat.dev); if (e.file()) |f| f.notreg = !self.stat.dir and !self.stat.reg; // TODO: Handle the scenario where we don't know the hard link count // (i.e. on imports from old ncdu versions that don't have the "nlink" field) @@ -249,8 +249,8 @@ const Context = struct { if (self.items_seen == 0) model.root = e.dir().? else { - try e.insert(p); - if (e.dir()) |d| try p.push(d); // Enter the directory + e.insert(p); + if (e.dir()) |d| p.push(d); // Enter the directory } } else if (self.wr) |wr| { @@ -286,8 +286,7 @@ const Context = struct { var active_context: ?*Context = null; // Read and index entries of the given dir. -// (TODO: shouldn't error on OOM but instead call a function that waits or something) -fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) (std.fs.File.Writer.Error || std.mem.Allocator.Error)!void { +fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) std.fs.File.Writer.Error!void { // XXX: The iterator allocates 8k+ bytes on the stack, may want to do heap allocation here? var it = dir.iterate(); while(true) { @@ -297,9 +296,9 @@ fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) (std.fs.File.Writer.Err } orelse break; ctx.stat.dir = false; - try ctx.pushPath(entry.name); + ctx.pushPath(entry.name); defer ctx.popPath(); - try main.handleEvent(false, false); + main.handleEvent(false, false); // XXX: This algorithm is extremely slow, can be optimized with some clever pattern parsing. const excluded = blk: { @@ -378,7 +377,7 @@ pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void { const full_path = std.fs.realpathAlloc(main.allocator, path) catch null; defer if (full_path) |p| main.allocator.free(p); - try ctx.pushPath(full_path orelse path); + ctx.pushPath(full_path orelse path); ctx.stat = try Stat.read(std.fs.cwd(), ctx.pathZ(), true); if (!ctx.stat.dir) return error.NotADirectory; @@ -703,7 +702,7 @@ const Import = struct { else => self.die("expected ',' or '}'"), } } - if (name) |n| self.ctx.pushPath(n) catch unreachable + if (name) |n| self.ctx.pushPath(n) else self.die("missing \"name\" field"); if (special) |s| self.ctx.addSpecial(s) catch unreachable else self.ctx.addStat(dir_dev) catch unreachable; @@ -733,7 +732,7 @@ const Import = struct { self.ctx.popPath(); if ((self.ctx.items_seen & 1023) == 0) - main.handleEvent(false, false) catch unreachable; + main.handleEvent(false, false); } fn root(self: *Self) void { @@ -791,7 +790,7 @@ pub fn importRoot(path: [:0]const u8, out: ?std.fs.File) !void { var animation_pos: u32 = 0; var need_confirm_quit = false; -fn drawBox() !void { +fn drawBox() void { ui.init(); const ctx = active_context.?; const width = saturateSub(ui.cols, 5); @@ -808,7 +807,7 @@ fn drawBox() !void { box.move(3, 2); ui.addstr("Current item: "); - ui.addstr(try ui.shorten(try ui.toUtf8(ctx.pathZ()), saturateSub(width, 18))); + ui.addstr(ui.shorten(ui.toUtf8(ctx.pathZ()), saturateSub(width, 18))); if (ctx.last_error) |path| { box.move(5, 2); @@ -816,7 +815,7 @@ fn drawBox() !void { ui.addstr("Warning: "); ui.style(.default); ui.addstr("error scanning "); - ui.addstr(try ui.shorten(try ui.toUtf8(path), saturateSub(width, 28))); + ui.addstr(ui.shorten(ui.toUtf8(path), saturateSub(width, 28))); box.move(6, 3); ui.addstr("some directory sizes may not be correct."); } @@ -855,7 +854,7 @@ fn drawBox() !void { } } -pub fn draw() !void { +pub fn draw() void { switch (main.config.scan_ui) { .none => {}, .line => { @@ -873,11 +872,11 @@ pub fn draw() !void { } _ = std.io.getStdErr().write(line) catch {}; }, - .full => try drawBox(), + .full => drawBox(), } } -pub fn keyInput(ch: i32) !void { +pub fn keyInput(ch: i32) void { if (need_confirm_quit) { switch (ch) { 'y', 'Y' => if (need_confirm_quit) ui.quit(), diff --git a/src/ui.zig b/src/ui.zig index 4a2f7610549b3bc27dc04c3e2d584a31d614c974..f5b7a0749ad575040d23b8ba63e805605cd5b790 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -29,6 +29,24 @@ pub fn quit() noreturn { std.process.exit(0); } +// Should be called when malloc fails. Will show a message to the user, wait +// for a second and return to give it another try. +// Glitch: this function may be called while we're in the process of drawing +// the ncurses window, in which case the deinit/reinit will cause the already +// drawn part to be discarded. A redraw will fix that, but that tends to only +// happen after user input. +// Also, init() and other ncurses-related functions may have hidden allocation, +// no clue if ncurses will consistently report OOM, but we're not handling that +// right now. +pub fn oom() void { + const haveui = inited; + deinit(); + _ = std.io.getStdErr().writer().writeAll("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8") catch {}; + std.time.sleep(std.time.ns_per_s); + if (haveui) + init(); +} + var to_utf8_buf = std.ArrayList(u8).init(main.allocator); fn toUtf8BadChar(ch: u8) bool { @@ -44,7 +62,7 @@ fn toUtf8BadChar(ch: u8) bool { // internal buffer that will be invalidated on the next call. // (Doesn't check for non-printable Unicode characters) // (This program assumes that the console locale is UTF-8, but file names may not be) -pub fn toUtf8(in: [:0]const u8) ![:0]const u8 { +pub fn toUtf8(in: [:0]const u8) [:0]const u8 { const hasBadChar = blk: { for (in) |ch| if (toUtf8BadChar(ch)) break :blk true; break :blk false; @@ -56,16 +74,16 @@ pub fn toUtf8(in: [:0]const u8) ![:0]const u8 { if (std.unicode.utf8ByteSequenceLength(in[i])) |cp_len| { if (!toUtf8BadChar(in[i]) and i + cp_len <= in.len) { if (std.unicode.utf8Decode(in[i .. i + cp_len])) |_| { - try to_utf8_buf.appendSlice(in[i .. i + cp_len]); + to_utf8_buf.appendSlice(in[i .. i + cp_len]) catch unreachable; i += cp_len; continue; } else |_| {} } } else |_| {} - try to_utf8_buf.writer().print("\\x{X:0>2}", .{in[i]}); + to_utf8_buf.writer().print("\\x{X:0>2}", .{in[i]}) catch unreachable; i += 1; } - return try arrayListBufZ(&to_utf8_buf); + return arrayListBufZ(&to_utf8_buf); } var shorten_buf = std.ArrayList(u8).init(main.allocator); @@ -75,7 +93,7 @@ var shorten_buf = std.ArrayList(u8).init(main.allocator); // Input is assumed to be valid UTF-8. // Return value points to the input string or to an internal buffer that is // invalidated on a subsequent call. -pub fn shorten(in: [:0]const u8, max_width: u32) ![:0] const u8 { +pub fn shorten(in: [:0]const u8, max_width: u32) [:0] const u8 { if (max_width < 4) return "..."; var total_width: u32 = 0; var prefix_width: u32 = 0; @@ -98,8 +116,8 @@ pub fn shorten(in: [:0]const u8, max_width: u32) ![:0] const u8 { if (total_width <= max_width) return in; shorten_buf.shrinkRetainingCapacity(0); - try shorten_buf.appendSlice(in[0..prefix_end]); - try shorten_buf.appendSlice("..."); + shorten_buf.appendSlice(in[0..prefix_end]) catch unreachable; + shorten_buf.appendSlice("...") catch unreachable; var start_width: u32 = prefix_width; var start_len: u32 = prefix_end; @@ -111,15 +129,15 @@ pub fn shorten(in: [:0]const u8, max_width: u32) ![:0] const u8 { start_width += cp_width; start_len += cp_len; if (total_width - start_width <= max_width - prefix_width - 3) { - try shorten_buf.appendSlice(in[start_len..]); + shorten_buf.appendSlice(in[start_len..]) catch unreachable; break; } } - return try arrayListBufZ(&shorten_buf); + return arrayListBufZ(&shorten_buf); } fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) !void { - try std.testing.expectEqualStrings(out, try shorten(in, max_width)); + try std.testing.expectEqualStrings(out, shorten(in, max_width)); } test "shorten" { diff --git a/src/util.zig b/src/util.zig index 9b73de57351232738841fb262e43f41415548464..277a0069c42da3402557efea34310e67859e3775 100644 --- a/src/util.zig +++ b/src/util.zig @@ -38,8 +38,8 @@ pub fn blocksToSize(b: u64) u64 { // Ensure the given arraylist buffer gets zero-terminated and returns a slice // into the buffer. The returned buffer is invalidated whenever the arraylist // is freed or written to. -pub fn arrayListBufZ(buf: *std.ArrayList(u8)) ![:0]const u8 { - try buf.append(0); +pub fn arrayListBufZ(buf: *std.ArrayList(u8)) [:0]const u8 { + buf.append(0) catch unreachable; defer buf.items.len -= 1; return buf.items[0..buf.items.len-1:0]; }