magdalena

Log | Files | Refs | README | LICENSE

commit 42bf945818946a95a481c92fb101235a2945396a
parent 208223b916c59db30ee10b33e5f9bef9374d2a3e
Author: mtmn <miro@haravara.org>
Date:   Wed, 10 Jun 2026 01:30:46 +0200

feat: apply findings from zlint

Diffstat:
Msrc/cfg.zig | 2+-
Msrc/db.zig | 50+++++++++++++++++++++++++-------------------------
Msrc/fzf.zig | 73+++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/io.zig | 27+++++++++++++++++++--------
Msrc/main.zig | 5++---
Msrc/memcached.zig | 34++++++++++++++++------------------
6 files changed, 98 insertions(+), 93 deletions(-)

diff --git a/src/cfg.zig b/src/cfg.zig @@ -51,7 +51,7 @@ pub fn loadConfig(allocator: std.mem.Allocator) ?std.json.Parsed(Config) { const path = std.fs.path.join(allocator, &.{ home, ".config", "magdalena", "config.json" }) catch return null; defer allocator.free(path); - const content = std.Io.Dir.cwd().readFileAlloc(io.rt, path, allocator, .unlimited) catch return null; + const content = std.Io.Dir.cwd().readFileAlloc(io.rt(), path, allocator, .unlimited) catch return null; defer allocator.free(content); return std.json.parseFromSlice(Config, allocator, content, .{ diff --git a/src/db.zig b/src/db.zig @@ -42,20 +42,20 @@ pub const Db = struct { .files_path = files_path, }; - std.Io.Dir.cwd().createDirPath(io.rt, base_path) catch |err| { + std.Io.Dir.cwd().createDirPath(io.rt(), base_path) catch |err| { if (err != error.PathAlreadyExists) return err; }; for (&[_][]const u8{ dirs_path, files_path }) |p| { - var f = std.Io.Dir.cwd().openFile(io.rt, p, .{ .mode = .read_write }) catch |err| { + var f = std.Io.Dir.cwd().openFile(io.rt(), p, .{ .mode = .read_write }) catch |err| { if (err == error.FileNotFound) { - const created = try std.Io.Dir.cwd().createFile(io.rt, p, .{ .truncate = false }); - created.close(io.rt); + const created = try std.Io.Dir.cwd().createFile(io.rt(), p, .{ .truncate = false }); + created.close(io.rt()); continue; } return err; }; - f.close(io.rt); + f.close(io.rt()); } return res; @@ -68,7 +68,7 @@ pub const Db = struct { } pub fn recentDirs(self: *Db) !zul.Managed([]DirectoryEntry) { - const content = std.Io.Dir.cwd().readFileAlloc(io.rt, self.dirs_path, self.allocator, .limited(64 * 1024 * 1024)) catch |err| { + const content = std.Io.Dir.cwd().readFileAlloc(io.rt(), self.dirs_path, self.allocator, .limited(64 * 1024 * 1024)) catch |err| { if (err == error.FileNotFound) return zul.Managed([]DirectoryEntry).fromJson(try std.json.parseFromSlice([]DirectoryEntry, self.allocator, "[]", .{})); return err; }; @@ -88,7 +88,7 @@ pub const Db = struct { } pub fn recentFiles(self: *Db) !zul.Managed([]FileEntry) { - const content = std.Io.Dir.cwd().readFileAlloc(io.rt, self.files_path, self.allocator, .limited(64 * 1024 * 1024)) catch |err| { + const content = std.Io.Dir.cwd().readFileAlloc(io.rt(), self.files_path, self.allocator, .limited(64 * 1024 * 1024)) catch |err| { if (err == error.FileNotFound) return zul.Managed([]FileEntry).fromJson(try std.json.parseFromSlice([]FileEntry, self.allocator, "[]", .{})); return err; }; @@ -153,7 +153,7 @@ pub const Db = struct { } fn resolvePath(self: *Db, path: []const u8) ![]const u8 { - return std.Io.Dir.cwd().realPathFileAlloc(io.rt, path, self.allocator) catch { + return std.Io.Dir.cwd().realPathFileAlloc(io.rt(), path, self.allocator) catch { return try std.fs.path.resolve(self.allocator, &[_][]const u8{path}); }; } @@ -162,21 +162,21 @@ pub const Db = struct { const abs_path = try self.resolvePath(dir_path); defer self.allocator.free(abs_path); - const file = std.Io.Dir.cwd().openFile(io.rt, self.dirs_path, .{ .mode = .write_only }) catch |err| switch (err) { - error.FileNotFound => try std.Io.Dir.cwd().createFile(io.rt, self.dirs_path, .{ .truncate = false }), + const file = std.Io.Dir.cwd().openFile(io.rt(), self.dirs_path, .{ .mode = .write_only }) catch |err| switch (err) { + error.FileNotFound => try std.Io.Dir.cwd().createFile(io.rt(), self.dirs_path, .{ .truncate = false }), else => return err, }; - defer file.close(io.rt); + defer file.close(io.rt()); - const now = zul.DateTime.now(io.rt); + const now = zul.DateTime.now(io.rt()); var ts_buf: [64]u8 = undefined; var ts_writer = std.Io.Writer.fixed(&ts_buf); try now.format(&ts_writer); const ts = ts_buf[0..ts_writer.end]; var fbuf: [4096]u8 = undefined; - var fw = file.writer(io.rt, &fbuf); - try fw.seekTo(try file.length(io.rt)); + var fw = file.writer(io.rt(), &fbuf); + try fw.seekTo(try file.length(io.rt())); try fw.interface.print("{s}|{s}\n", .{ ts, abs_path }); try fw.interface.flush(); } @@ -185,21 +185,21 @@ pub const Db = struct { const abs_path = try self.resolvePath(file_path); defer self.allocator.free(abs_path); - const file = std.Io.Dir.cwd().openFile(io.rt, self.files_path, .{ .mode = .write_only }) catch |err| switch (err) { - error.FileNotFound => try std.Io.Dir.cwd().createFile(io.rt, self.files_path, .{ .truncate = false }), + const file = std.Io.Dir.cwd().openFile(io.rt(), self.files_path, .{ .mode = .write_only }) catch |err| switch (err) { + error.FileNotFound => try std.Io.Dir.cwd().createFile(io.rt(), self.files_path, .{ .truncate = false }), else => return err, }; - defer file.close(io.rt); + defer file.close(io.rt()); - const now = zul.DateTime.now(io.rt); + const now = zul.DateTime.now(io.rt()); var ts_buf: [64]u8 = undefined; var ts_writer = std.Io.Writer.fixed(&ts_buf); try now.format(&ts_writer); const ts = ts_buf[0..ts_writer.end]; var fbuf: [4096]u8 = undefined; - var fw = file.writer(io.rt, &fbuf); - try fw.seekTo(try file.length(io.rt)); + var fw = file.writer(io.rt(), &fbuf); + try fw.seekTo(try file.length(io.rt())); try fw.interface.print("{s}|{s}|{s}|{s}\n", .{ ts, file_type, action, abs_path }); try fw.interface.flush(); } @@ -212,7 +212,7 @@ pub const Db = struct { fn cleanupFile(self: *Db, path: []const u8, path_col_idx: usize) !void { io.warn("Cleaning up {s}...\n", .{path}); - const content = try std.Io.Dir.cwd().readFileAlloc(io.rt, path, self.allocator, .limited(64 * 1024 * 1024)); + const content = try std.Io.Dir.cwd().readFileAlloc(io.rt(), path, self.allocator, .limited(64 * 1024 * 1024)); defer self.allocator.free(content); var seen = std.StringHashMap(void).init(self.allocator); @@ -243,7 +243,7 @@ pub const Db = struct { if (seen.contains(p)) continue; - std.Io.Dir.cwd().access(io.rt, p, .{}) catch |err| { + std.Io.Dir.cwd().access(io.rt(), p, .{}) catch |err| { if (err == error.FileNotFound) { missing_entries += 1; continue; @@ -258,11 +258,11 @@ pub const Db = struct { const duplicates = total_entries - lines.items.len - missing_entries; io.warn(" Entries: {d} total, {d} unique, {d} missing, {d} duplicates removed\n", .{ total_entries, lines.items.len, missing_entries, duplicates }); - const write_file = try std.Io.Dir.cwd().createFile(io.rt, path, .{ .truncate = true }); - defer write_file.close(io.rt); + const write_file = try std.Io.Dir.cwd().createFile(io.rt(), path, .{ .truncate = true }); + defer write_file.close(io.rt()); var write_buf: [4096]u8 = undefined; - var writer = write_file.writer(io.rt, &write_buf); + var writer = write_file.writer(io.rt(), &write_buf); var i: usize = lines.items.len; while (i > 0) { diff --git a/src/fzf.zig b/src/fzf.zig @@ -26,32 +26,31 @@ fn gotoFile(allocator: std.mem.Allocator, db: ?*hist.Db, file_path: []const u8, d.logFile(file_path, ext, action) catch |err| io.warn("failed to log file: {}\n", .{err}); } - if (std.Io.Dir.openFileAbsolute(io.rt, "/dev/tty", .{ .mode = .read_write })) |tty| { + if (std.Io.Dir.openFileAbsolute(io.rt(), "/dev/tty", .{ .mode = .read_write })) |tty| { if (std.c.dup2(tty.handle, std.posix.STDIN_FILENO) < 0) io.warn("failed to redirect stdin to tty\n", .{}); if (std.c.dup2(tty.handle, std.posix.STDOUT_FILENO) < 0) io.warn("failed to redirect stdout to tty\n", .{}); - tty.close(io.rt); + tty.close(io.rt()); } else |err| { io.warn("failed to open /dev/tty: {}\n", .{err}); } if (command) |cmd| { - return std.process.replace(io.rt, .{ .argv = &.{ cmd, file_path } }); + return std.process.replace(io.rt(), .{ .argv = &.{ cmd, file_path } }); } const editor = config.editor orelse (io.getenv("EDITOR") orelse return error.EditorNotSet); if (line) |l| { const line_arg = try std.fmt.allocPrint(allocator, "+{s}", .{l}); - return std.process.replace(io.rt, .{ .argv = &.{ editor, line_arg, file_path } }); + return std.process.replace(io.rt(), .{ .argv = &.{ editor, line_arg, file_path } }); } - return std.process.replace(io.rt, .{ .argv = &.{ editor, file_path } }); + return std.process.replace(io.rt(), .{ .argv = &.{ editor, file_path } }); } const StdIo = std.process.SpawnOptions.StdIo; const Fzf = struct { - child: std.process.Child = undefined, argv: [][]const u8, allocator: std.mem.Allocator, stdin: StdIo = .pipe, @@ -80,8 +79,8 @@ const Fzf = struct { }; } - pub fn spawn(self: *Fzf) !void { - self.child = try std.process.spawn(io.rt, .{ + pub fn spawn(self: *Fzf) !std.process.Child { + return std.process.spawn(io.rt(), .{ .argv = self.argv, .stdin = self.stdin, .stdout = self.stdout, @@ -110,21 +109,20 @@ fn shouldSkip(path: []const u8, ignored_patterns: []const []const u8) bool { fn runFzfPicker(allocator: std.mem.Allocator, fzf_opts: []const []const u8, items: []const []const u8) ![]u8 { var fzf = try Fzf.init(allocator, fzf_opts, &[_][]const u8{}); defer fzf.deinit(); - try fzf.spawn(); - const child = &fzf.child; + var child = try fzf.spawn(); if (child.stdin) |stdin| { for (items) |item| { - stdin.writeStreamingAll(io.rt, item) catch |err| { + stdin.writeStreamingAll(io.rt(), item) catch |err| { if (err == error.BrokenPipe) break; return err; }; - stdin.writeStreamingAll(io.rt, "\n") catch |err| { + stdin.writeStreamingAll(io.rt(), "\n") catch |err| { if (err == error.BrokenPipe) break; return err; }; } - stdin.close(io.rt); + stdin.close(io.rt()); child.stdin = null; } @@ -133,11 +131,11 @@ fn runFzfPicker(allocator: std.mem.Allocator, fzf_opts: []const []const u8, item if (child.stdout) |stdout| { var buffer: [4096]u8 = undefined; - var sr = stdout.reader(io.rt, &buffer); + var sr = stdout.reader(io.rt(), &buffer); stdout_data = try sr.interface.allocRemaining(allocator, .unlimited); } - const term = try child.wait(io.rt); + const term = try child.wait(io.rt()); if (term != .exited or term.exited != 0) return error.UserAbort; const selected = std.mem.trim(u8, stdout_data orelse "", " \n\r\t"); if (selected.len == 0) return error.UserAbort; @@ -166,7 +164,7 @@ pub fn recentDir(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg. const selected = try runFzfPicker(allocator, config.fzf_opts, items.items); defer allocator.free(selected); - std.Io.Dir.accessAbsolute(io.rt, selected, .{}) catch |err| { + std.Io.Dir.accessAbsolute(io.rt(), selected, .{}) catch |err| { io.warn("Selected directory no longer exists: {s} ({})\n", .{ selected, err }); return error.UserAbort; }; @@ -198,8 +196,8 @@ pub fn favorites(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg. defer allocator.free(selected); const is_dir = blk: { - var dir = std.Io.Dir.openDirAbsolute(io.rt, selected, .{}) catch break :blk false; - dir.close(io.rt); + var dir = std.Io.Dir.openDirAbsolute(io.rt(), selected, .{}) catch break :blk false; + dir.close(io.rt()); break :blk true; }; @@ -254,7 +252,7 @@ pub fn recentFile(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg const selected = try runFzfPicker(allocator, config.fzf_opts, items.items); defer allocator.free(selected); - try std.Io.Dir.accessAbsolute(io.rt, selected, .{}); + try std.Io.Dir.accessAbsolute(io.rt(), selected, .{}); try gotoFile(allocator, db, selected, null, config); } @@ -294,19 +292,18 @@ pub fn grep(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.Confi fzf.stdout = .pipe; fzf.stderr = .inherit; - try fzf.spawn(); - const child = &fzf.child; + var child = try fzf.spawn(); var stdout_data: ?[]u8 = null; defer if (stdout_data) |d| allocator.free(d); if (child.stdout) |stdout| { var buffer: [4096]u8 = undefined; - var sr = stdout.reader(io.rt, &buffer); + var sr = stdout.reader(io.rt(), &buffer); stdout_data = try sr.interface.allocRemaining(allocator, .unlimited); } - const term = try child.wait(io.rt); + const term = try child.wait(io.rt()); if (term != .exited or term.exited != 0) return error.UserAbort; const selected = std.mem.trim(u8, stdout_data orelse "", " \n\r\t"); if (selected.len == 0) return error.UserAbort; @@ -334,12 +331,12 @@ const Entry = struct { fn getMaxDirMtime(ignored_patterns: []const []const u8, max_depth: usize) !i128 { var max_mtime: i128 = 0; - if (std.Io.Dir.cwd().statFile(io.rt, ".", .{})) |stat| { + if (std.Io.Dir.cwd().statFile(io.rt(), ".", .{})) |stat| { max_mtime = stat.mtime.nanoseconds; } else |_| {} - var root = try std.Io.Dir.cwd().openDir(io.rt, ".", .{ .iterate = true }); - defer root.close(io.rt); + var root = try std.Io.Dir.cwd().openDir(io.rt(), ".", .{ .iterate = true }); + defer root.close(io.rt()); try scanMaxMtime(&root, 0, max_depth, ignored_patterns, &max_mtime); return max_mtime; @@ -349,7 +346,7 @@ fn getMaxDirMtime(ignored_patterns: []const []const u8, max_depth: usize) !i128 // once by its own `defer` as the recursion unwinds. fn scanMaxMtime(dir: *std.Io.Dir, depth: usize, max_depth: usize, ignored_patterns: []const []const u8, max_mtime: *i128) !void { var it = dir.iterate(); - while (try it.next(io.rt)) |entry| { + while (try it.next(io.rt())) |entry| { var skip = false; for (ignored_patterns) |pattern| { if (std.mem.eql(u8, entry.name, pattern)) { @@ -361,7 +358,7 @@ fn scanMaxMtime(dir: *std.Io.Dir, depth: usize, max_depth: usize, ignored_patter const is_dir = entry.kind == .directory; if (is_dir or entry.kind == .sym_link) { - const stat = dir.statFile(io.rt, entry.name, .{}) catch |err| { + const stat = dir.statFile(io.rt(), entry.name, .{}) catch |err| { if (err != error.FileNotFound and err != error.AccessDenied) { io.warn("failed to stat {s}: {}\n", .{ entry.name, err }); } @@ -371,11 +368,11 @@ fn scanMaxMtime(dir: *std.Io.Dir, depth: usize, max_depth: usize, ignored_patter if (stat.mtime.nanoseconds > max_mtime.*) max_mtime.* = stat.mtime.nanoseconds; if (is_dir and depth + 1 < max_depth) { - var sub_dir = dir.openDir(io.rt, entry.name, .{ .iterate = true }) catch |err| { + var sub_dir = dir.openDir(io.rt(), entry.name, .{ .iterate = true }) catch |err| { io.warn("failed to open directory {s}: {}\n", .{ entry.name, err }); continue; }; - defer sub_dir.close(io.rt); + defer sub_dir.close(io.rt()); try scanMaxMtime(&sub_dir, depth + 1, max_depth, ignored_patterns, max_mtime); } } @@ -396,7 +393,7 @@ fn collectEntries( entries: *std.ArrayListUnmanaged(Entry), ) !void { var it = dir.iterate(); - while (try it.next(io.rt)) |entry| { + while (try it.next(io.rt())) |entry| { const entry_path = if (prefix) |p| try std.fs.path.join(allocator, &.{ p, entry.name }) else @@ -406,7 +403,7 @@ fn collectEntries( if (shouldSkip(entry_path, ignored_patterns)) continue; if (entry.kind == kind or entry.kind == .sym_link) { - if (dir.statFile(io.rt, entry.name, .{})) |stat| { + if (dir.statFile(io.rt(), entry.name, .{})) |stat| { if (stat.kind == kind) { try entries.append(allocator, .{ .path = try allocator.dupe(u8, entry_path), @@ -421,11 +418,11 @@ fn collectEntries( } if (entry.kind == .directory and depth + 1 < max_depth) { - var sub_dir = dir.openDir(io.rt, entry.name, .{ .iterate = true }) catch |err| { + var sub_dir = dir.openDir(io.rt(), entry.name, .{ .iterate = true }) catch |err| { io.warn("failed to open directory {s}: {}\n", .{ entry.name, err }); continue; }; - defer sub_dir.close(io.rt); + defer sub_dir.close(io.rt()); try collectEntries(allocator, &sub_dir, entry_path, depth + 1, max_depth, kind, ignored_patterns, entries); } } @@ -455,7 +452,7 @@ fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.Io.Fil var cache_hit = false; if (mc) |*m| { const cwd = blk: { - break :blk std.process.currentPathAlloc(io.rt, allocator) catch |err| { + break :blk std.process.currentPathAlloc(io.rt(), allocator) catch |err| { io.warn("failed to get cwd for cache: {}\n", .{err}); break :blk null; }; @@ -488,8 +485,8 @@ fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.Io.Fil } if (!cache_hit) { - var root = try std.Io.Dir.cwd().openDir(io.rt, ".", .{ .iterate = true }); - defer root.close(io.rt); + var root = try std.Io.Dir.cwd().openDir(io.rt(), ".", .{ .iterate = true }); + defer root.close(io.rt()); try collectEntries(allocator, &root, null, 0, max_depth, kind, config.ignored_patterns, &entries); if (mc) |*m| { @@ -519,7 +516,7 @@ fn runFzfWithWalker(allocator: std.mem.Allocator, db: *hist.Db, kind: std.Io.Fil const clean_path = if (std.mem.startsWith(u8, selected, "./")) selected[2..] else selected; if (clean_path.len > 0) { - std.Io.Dir.cwd().access(io.rt, clean_path, .{}) catch |err| { + std.Io.Dir.cwd().access(io.rt(), clean_path, .{}) catch |err| { io.warn("Selected item no longer exists or is inaccessible: {s} ({})\n", .{ clean_path, err }); return error.UserAbort; }; diff --git a/src/io.zig b/src/io.zig @@ -1,13 +1,24 @@ const std = @import("std"); -// Process-wide handles, set once from `std.process.Init` in `main`. The app is -// a short-lived single-threaded CLI, so threading these through every call site -// would be pure noise; a couple of globals keep the IO surface small. -pub var rt: std.Io = undefined; -pub var env: *std.process.Environ.Map = undefined; +// Process-wide handles, populated once from `std.process.Init` in `main`. The +// app is a short-lived single-threaded CLI, so threading these through every +// call site would be pure noise; ambient state keeps the IO surface small. +// They are optionals (not `undefined`) so that any use before `init` is a +// checked unwrap panic rather than undefined behavior. +var rt_handle: ?std.Io = null; +var env_map: ?*std.process.Environ.Map = null; + +pub fn init(io_handle: std.Io, environ: *std.process.Environ.Map) void { + rt_handle = io_handle; + env_map = environ; +} + +pub fn rt() std.Io { + return rt_handle.?; +} pub fn getenv(key: []const u8) ?[]const u8 { - return env.get(key); + return env_map.?.get(key); } pub const Writer = struct { @@ -38,12 +49,12 @@ pub const Writer = struct { }; pub fn getStdout(buf: []u8) Writer { - return .{ .inner = std.Io.File.stdout().writerStreaming(rt, buf) }; + return .{ .inner = std.Io.File.stdout().writerStreaming(rt(), buf) }; } pub fn warn(comptime fmt: []const u8, args: anytype) void { var buf: [4096]u8 = undefined; - var writer = std.Io.File.stderr().writerStreaming(rt, &buf); + var writer = std.Io.File.stderr().writerStreaming(rt(), &buf); writer.interface.print(fmt, args) catch |err| { std.log.warn("failed to write to stderr: {}", .{err}); }; diff --git a/src/main.zig b/src/main.zig @@ -13,8 +13,7 @@ test { } pub fn main(init: std.process.Init) void { - io.rt = init.io; - io.env = init.environ_map; + io.init(init.io, init.environ_map); const allocator = init.gpa; const args = cli.parseArgs(allocator, init.minimal.args) catch |err| { @@ -36,7 +35,7 @@ pub fn main(init: std.process.Init) void { var cwd_buf: [std.fs.max_path_bytes]u8 = undefined; const cwd = blk: { - const n = std.process.currentPath(io.rt, &cwd_buf) catch |err| { + const n = std.process.currentPath(io.rt(), &cwd_buf) catch |err| { io.warn("failed to get cwd for folder overrides: {}\n", .{err}); break :blk ""; }; diff --git a/src/memcached.zig b/src/memcached.zig @@ -4,15 +4,17 @@ const io = @import("io.zig"); pub const Memcached = struct { allocator: std.mem.Allocator, stream: std.Io.net.Stream, - read_buf: [65536]u8 = undefined, - write_buf: [4096]u8 = undefined, - reader: std.Io.net.Stream.Reader = undefined, - writer: std.Io.net.Stream.Writer = undefined, - started: bool = false, + read_buf: [65536]u8 = @splat(0), + write_buf: [4096]u8 = @splat(0), + // `null` until first use; the Stream's buffered Reader/Writer hold pointers + // into this struct's own buffers, so they can only be created once the + // struct sits at its final address (i.e. after `init` returns by value). + reader: ?std.Io.net.Stream.Reader = null, + writer: ?std.Io.net.Stream.Writer = null, pub fn init(allocator: std.mem.Allocator, host: []const u8, port: u16) !Memcached { const address = try std.Io.net.IpAddress.parseIp4(host, port); - const stream = try address.connect(io.rt, .{ .mode = .stream }); + const stream = try address.connect(io.rt(), .{ .mode = .stream }); return .{ .allocator = allocator, .stream = stream, @@ -20,32 +22,28 @@ pub const Memcached = struct { } pub fn deinit(self: *Memcached) void { - if (self.started) { - self.writer.interface.flush() catch |err| { + if (self.writer) |*writer| { + writer.interface.flush() catch |err| { io.warn("failed to flush memcached on deinit: {}\n", .{err}); }; } - self.stream.close(io.rt); + self.stream.close(io.rt()); } - // The Stream's buffered Reader/Writer hold pointers into this struct's own - // buffers, so they must be initialized only once the struct sits at its - // final address (i.e. after `init` returns by value). fn ensureStarted(self: *Memcached) void { - if (self.started) return; - self.reader = self.stream.reader(io.rt, &self.read_buf); - self.writer = self.stream.writer(io.rt, &self.write_buf); - self.started = true; + if (self.reader != null) return; + self.reader = self.stream.reader(io.rt(), &self.read_buf); + self.writer = self.stream.writer(io.rt(), &self.write_buf); } fn r(self: *Memcached) *std.Io.Reader { self.ensureStarted(); - return &self.reader.interface; + return &self.reader.?.interface; } fn w(self: *Memcached) *std.Io.Writer { self.ensureStarted(); - return &self.writer.interface; + return &self.writer.?.interface; } fn flushWrite(self: *Memcached) !void {