Commit bc2cf0c173

Andrew Kelley <andrew@ziglang.org>
2025-07-09 02:33:02
eliminate all uses of std.io.Writer.count except for CBE
1 parent d345a10
Changed files (8)
lib/std/http/Client.zig
@@ -1293,13 +1293,14 @@ pub const basic_authorization = struct {
         const user: Uri.Component = uri.user orelse .empty;
         const password: Uri.Component = uri.password orelse .empty;
 
-        var w: std.io.Writer = .discarding(&.{});
-        user.formatUser(&w) catch unreachable; // discarding
-        const user_len = w.count;
+        var dw: std.io.Writer.Discarding = .init(&.{});
+        user.formatUser(&dw.writer) catch unreachable; // discarding
+        const user_len = dw.count + dw.writer.end;
 
-        w.count = 0;
-        password.formatPassword(&w) catch unreachable; // discarding
-        const password_len = w.count;
+        dw.count = 0;
+        dw.writer.end = 0;
+        password.formatPassword(&dw.writer) catch unreachable; // discarding
+        const password_len = dw.count + dw.writer.end;
 
         return valueLength(@intCast(user_len), @intCast(password_len));
     }
@@ -1311,7 +1312,6 @@ pub const basic_authorization = struct {
         var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;
         var w: std.io.Writer = .fixed(&buf);
         user.formatUser(&w) catch unreachable; // fixed
-        assert(w.count <= max_user_len);
         password.formatPassword(&w) catch unreachable; // fixed
 
         @memcpy(out[0..prefix.len], prefix);
lib/std/io/Reader.zig
@@ -132,10 +132,8 @@ pub fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
         r.seek += n;
         return n;
     }
-    const before = w.count;
     const n = try r.vtable.stream(r, w, limit);
     assert(n <= @intFromEnum(limit));
-    assert(w.count == before + n);
     return n;
 }
 
@@ -158,17 +156,17 @@ pub fn discard(r: *Reader, limit: Limit) Error!usize {
 pub fn defaultDiscard(r: *Reader, limit: Limit) Error!usize {
     assert(r.seek == 0);
     assert(r.end == 0);
-    var w: Writer = .discarding(r.buffer);
-    const n = r.stream(&w, limit) catch |err| switch (err) {
+    var dw: Writer.Discarding = .init(r.buffer);
+    const n = r.stream(&dw.writer, limit) catch |err| switch (err) {
         error.WriteFailed => unreachable,
         error.ReadFailed => return error.ReadFailed,
         error.EndOfStream => return error.EndOfStream,
     };
     if (n > @intFromEnum(limit)) {
         const over_amt = n - @intFromEnum(limit);
-        r.seek = w.end - over_amt;
-        r.end = w.end;
-        assert(r.end <= w.buffer.len); // limit may be exceeded only by an amount within buffer capacity.
+        r.seek = dw.writer.end - over_amt;
+        r.end = dw.writer.end;
+        assert(r.end <= dw.writer.buffer.len); // limit may be exceeded only by an amount within buffer capacity.
         return @intFromEnum(limit);
     }
     return n;
lib/std/io/Writer.zig
@@ -14,12 +14,6 @@ vtable: *const VTable,
 buffer: []u8,
 /// In `buffer` before this are buffered bytes, after this is `undefined`.
 end: usize = 0,
-/// Tracks total number of bytes written to this `Writer`. This value
-/// only increases. In the case of fixed mode, this value always equals `end`.
-///
-/// This value is maintained by the interface; `VTable` function
-/// implementations need not modify it.
-count: usize = 0,
 
 pub const VTable = struct {
     /// Sends bytes to the logical sink. A write will only be sent here if it
@@ -117,8 +111,7 @@ pub const FileError = error{
     Unimplemented,
 };
 
-/// Writes to `buffer` and returns `error.WriteFailed` when it is full. Unless
-/// modified externally, `count` will always equal `end`.
+/// Writes to `buffer` and returns `error.WriteFailed` when it is full.
 pub fn fixed(buffer: []u8) Writer {
     return .{
         .vtable = &.{ .drain = fixedDrain },
@@ -137,16 +130,6 @@ pub const failing: Writer = .{
     },
 };
 
-pub fn discarding(buffer: []u8) Writer {
-    return .{
-        .vtable = &.{
-            .drain = discardingDrain,
-            .sendFile = discardingSendFile,
-        },
-        .buffer = buffer,
-    };
-}
-
 /// Returns the contents not yet drained.
 pub fn buffered(w: *const Writer) []u8 {
     return w.buffer[0..w.end];
@@ -178,12 +161,7 @@ pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usiz
     assert(data.len > 0);
     const buffer = w.buffer;
     const count = countSplat(data, splat);
-    if (w.end + count > buffer.len) {
-        const n = try w.vtable.drain(w, data, splat);
-        w.count += n;
-        return n;
-    }
-    w.count += count;
+    if (w.end + count > buffer.len) return w.vtable.drain(w, data, splat);
     for (data) |bytes| {
         @memcpy(buffer[w.end..][0..bytes.len], bytes);
         w.end += bytes.len;
@@ -236,7 +214,6 @@ pub fn writeSplatHeader(
     if (new_end <= w.buffer.len) {
         @memcpy(w.buffer[w.end..][0..header.len], header);
         w.end = new_end;
-        w.count += header.len;
         return header.len + try writeSplat(w, data, splat);
     }
     var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size.
@@ -249,9 +226,7 @@ pub fn writeSplatHeader(
         if (vecs.len - i == 0) break;
     }
     const new_splat = if (vecs[i - 1].ptr == data[data.len - 1].ptr) splat else 1;
-    const n = try w.vtable.drain(w, vecs[0..i], new_splat);
-    w.count += n;
-    return n;
+    return w.vtable.drain(w, vecs[0..i], new_splat);
 }
 
 /// Equivalent to `writeSplatHeader` but writes at most `limit` bytes.
@@ -429,7 +404,6 @@ pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {
 
 pub fn undo(w: *Writer, n: usize) void {
     w.end -= n;
-    w.count -= n;
 }
 
 /// After calling `writableSliceGreedy`, this function tracks how many bytes
@@ -440,13 +414,11 @@ pub fn advance(w: *Writer, n: usize) void {
     const new_end = w.end + n;
     assert(new_end <= w.buffer.len);
     w.end = new_end;
-    w.count += n;
 }
 
 /// After calling `writableVector`, this function tracks how many bytes were
 /// written to it.
 pub fn advanceVector(w: *Writer, n: usize) usize {
-    w.count += n;
     return consume(w, n);
 }
 
@@ -504,12 +476,9 @@ pub fn write(w: *Writer, bytes: []const u8) Error!usize {
         @branchHint(.likely);
         @memcpy(w.buffer[w.end..][0..bytes.len], bytes);
         w.end += bytes.len;
-        w.count += bytes.len;
         return bytes.len;
     }
-    const n = try w.vtable.drain(w, &.{bytes}, 1);
-    w.count += n;
-    return n;
+    return w.vtable.drain(w, &.{bytes}, 1);
 }
 
 /// Asserts `buffer` capacity exceeds `preserve_length`.
@@ -519,7 +488,6 @@ pub fn writePreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Erro
         @branchHint(.likely);
         @memcpy(w.buffer[w.end..][0..bytes.len], bytes);
         w.end += bytes.len;
-        w.count += bytes.len;
         return bytes.len;
     }
     const temp_end = w.end -| preserve_length;
@@ -527,7 +495,6 @@ pub fn writePreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Erro
     w.end = temp_end;
     defer w.end += preserved.len;
     const n = try w.vtable.drain(w, &.{bytes}, 1);
-    w.count += n;
     assert(w.end <= temp_end + preserved.len);
     @memmove(w.buffer[w.end..][0..preserved.len], preserved);
     return n;
@@ -560,15 +527,11 @@ pub fn print(w: *Writer, comptime format: []const u8, args: anytype) Error!void
 pub fn writeByte(w: *Writer, byte: u8) Error!void {
     while (w.buffer.len - w.end == 0) {
         const n = try w.vtable.drain(w, &.{&.{byte}}, 1);
-        if (n > 0) {
-            w.count += 1;
-            return;
-        }
+        if (n > 0) return;
     } else {
         @branchHint(.likely);
         w.buffer[w.end] = byte;
         w.end += 1;
-        w.count += 1;
     }
 }
 
@@ -581,7 +544,6 @@ pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!voi
         @branchHint(.likely);
         w.buffer[w.end] = byte;
         w.end += 1;
-        w.count += 1;
     }
 }
 
@@ -690,12 +652,10 @@ pub fn sendFileHeader(
     if (new_end <= w.buffer.len) {
         @memcpy(w.buffer[w.end..][0..header.len], header);
         w.end = new_end;
-        w.count += header.len;
         return header.len + try w.vtable.sendFile(w, file_reader, limit);
     }
     const buffered_contents = limit.slice(file_reader.interface.buffered());
     const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
-    w.count += n;
     file_reader.interface.toss(n - header.len);
     return n;
 }
@@ -1950,29 +1910,52 @@ pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) File
     return error.WriteFailed;
 }
 
-pub fn discardingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
-    const slice = data[0 .. data.len - 1];
-    const pattern = data[slice.len..];
-    var written: usize = pattern.len * splat;
-    for (slice) |bytes| written += bytes.len;
-    w.end = 0;
-    return written;
-}
+pub const Discarding = struct {
+    count: u64,
+    writer: Writer,
 
-pub fn discardingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
-    if (File.Handle == void) return error.Unimplemented;
-    w.end = 0;
-    if (file_reader.getSize()) |size| {
-        const n = limit.minInt64(size - file_reader.pos);
-        file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
+    pub fn init(buffer: []u8) Discarding {
+        return .{
+            .count = 0,
+            .writer = .{
+                .vtable = &.{
+                    .drain = Discarding.drain,
+                    .sendFile = Discarding.sendFile,
+                },
+                .buffer = buffer,
+            },
+        };
+    }
+
+    pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
+        const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
+        const slice = data[0 .. data.len - 1];
+        const pattern = data[slice.len..];
+        var written: usize = pattern.len * splat;
+        for (slice) |bytes| written += bytes.len;
+        d.count += w.end + written;
         w.end = 0;
-        return n;
-    } else |_| {
-        // Error is observable on `file_reader` instance, and it is better to
-        // treat the file as a pipe.
-        return error.Unimplemented;
+        return written;
     }
-}
+
+    pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
+        if (File.Handle == void) return error.Unimplemented;
+        const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
+        d.count += w.end;
+        w.end = 0;
+        if (file_reader.getSize()) |size| {
+            const n = limit.minInt64(size - file_reader.pos);
+            file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
+            w.end = 0;
+            d.count += n;
+            return n;
+        } else |_| {
+            // Error is observable on `file_reader` instance, and it is better to
+            // treat the file as a pipe.
+            return error.Unimplemented;
+        }
+    }
+};
 
 /// Removes the first `n` bytes from `buffer` by shifting buffer contents,
 /// returning how many bytes are left after consuming the entire buffer, or
@@ -2219,9 +2202,7 @@ pub const Allocating = struct {
     }
 
     pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void {
-        const shrink_by = a.writer.end - new_len;
         a.writer.end = new_len;
-        a.writer.count -= shrink_by;
     }
 
     pub fn clearRetainingCapacity(a: *Allocating) void {
lib/std/zig/ErrorBundle.zig
@@ -195,22 +195,30 @@ fn renderErrorMessageToWriter(
 ) (Writer.Error || std.posix.UnexpectedError)!void {
     const ttyconf = options.ttyconf;
     const err_msg = eb.getErrorMessage(err_msg_index);
-    const prefix_start = w.count;
     if (err_msg.src_loc != .none) {
         const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));
+        var prefix: std.io.Writer.Discarding = .init(&.{});
         try w.splatByteAll(' ', indent);
+        prefix.count += indent;
         try ttyconf.setColor(w, .bold);
         try w.print("{s}:{d}:{d}: ", .{
             eb.nullTerminatedString(src.data.src_path),
             src.data.line + 1,
             src.data.column + 1,
         });
+        try prefix.writer.print("{s}:{d}:{d}: ", .{
+            eb.nullTerminatedString(src.data.src_path),
+            src.data.line + 1,
+            src.data.column + 1,
+        });
         try ttyconf.setColor(w, color);
         try w.writeAll(kind);
+        prefix.count += kind.len;
         try w.writeAll(": ");
+        prefix.count += 2;
         // This is the length of the part before the error message:
         // e.g. "file.zig:4:5: error: "
-        const prefix_len = w.count - prefix_start;
+        const prefix_len: usize = @intCast(prefix.count);
         try ttyconf.setColor(w, .reset);
         try ttyconf.setColor(w, .bold);
         if (err_msg.count == 1) {
lib/std/debug.zig
@@ -1603,10 +1603,10 @@ test "manage resources correctly" {
     // self-hosted debug info is still too buggy
     if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest;
 
-    var writer: std.io.Writer = .discarding(&.{});
+    var discarding: std.io.Writer.Discarding = .init(&.{});
     var di = try SelfInfo.open(testing.allocator);
     defer di.deinit();
-    try printSourceAtAddress(&di, &writer, showMyTrace(), io.tty.detectConfig(.stderr()));
+    try printSourceAtAddress(&di, &discarding.writer, showMyTrace(), io.tty.detectConfig(.stderr()));
 }
 
 noinline fn showMyTrace() usize {
lib/std/fmt.zig
@@ -772,11 +772,11 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr
 /// Count the characters needed for format.
 pub fn count(comptime fmt: []const u8, args: anytype) usize {
     var trash_buffer: [64]u8 = undefined;
-    var w: Writer = .discarding(&trash_buffer);
-    w.print(fmt, args) catch |err| switch (err) {
+    var dw: Writer.Discarding = .init(&trash_buffer);
+    dw.writer.print(fmt, args) catch |err| switch (err) {
         error.WriteFailed => unreachable,
     };
-    return w.count;
+    return @intCast(dw.count + dw.writer.end);
 }
 
 pub fn allocPrint(gpa: Allocator, comptime fmt: []const u8, args: anytype) Allocator.Error![]u8 {
src/arch/x86_64/Encoding.zig
@@ -1016,8 +1016,8 @@ fn estimateInstructionLength(prefix: Prefix, encoding: Encoding, ops: []const Op
     // By using a buffer with maximum length of encoded instruction, we can use
     // the `end` field of the Writer for the count.
     var buf: [16]u8 = undefined;
-    var trash = std.io.Writer.discarding(&buf);
-    inst.encode(&trash, .{
+    var trash: std.io.Writer.Discarding = .init(&buf);
+    inst.encode(&trash.writer, .{
         .allow_frame_locs = true,
         .allow_symbols = true,
     }) catch {
@@ -1027,7 +1027,7 @@ fn estimateInstructionLength(prefix: Prefix, encoding: Encoding, ops: []const Op
         // (`estimateInstructionLength`) has the wrong function signature.
         @panic("unexpected failure to encode");
     };
-    return @intCast(trash.end);
+    return trash.writer.end;
 }
 
 const mnemonic_to_encodings_map = init: {
src/link/Elf/Atom.zig
@@ -1390,8 +1390,8 @@ const x86_64 = struct {
                     // TODO: hack to force imm32s in the assembler
                     .{ .imm = .s(-129) },
                 }, t) catch return false;
-                var trash = std.io.Writer.discarding(&.{});
-                inst.encode(&trash, .{}) catch return false;
+                var trash: std.io.Writer.Discarding = .init(&.{});
+                inst.encode(&trash.writer, .{}) catch return false;
                 return true;
             },
             else => return false,