Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/std/Io/Reader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ pub fn fixed(buffer: []const u8) Reader {
}

pub fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
if (limit == .nothing) {
@branchHint(.unlikely);
return 0;
}
Comment on lines +171 to +174

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be logic inside the interface. To enforce this rule, it must be an assertion. There's already an assertion below for this on line 183 but it will be skipped if an error is returned. I suggest instead of this to do the assertion after calling stream but before returning an error. Also, this rule must be mentioned in the VTable documentation for stream.

const buffer = limit.slice(r.buffer[r.seek..r.end]);
if (buffer.len > 0) {
@branchHint(.likely);
Expand Down Expand Up @@ -1329,6 +1333,14 @@ test fixed {
try testing.expectError(error.EndOfStream, r.takeByte());
}

test "fixed: when streaming 0 bytes, return 0" {
var r: Reader = .fixed("i'm dying");
var trash: Writer.Discarding = .init(&.{});

const n = try r.stream(&trash.writer, .nothing);
try testing.expectEqual(n, 0);
}

test peek {
var r: Reader = .fixed("abc");
try testing.expectEqualStrings("ab", try r.peek(2));
Expand Down
44 changes: 44 additions & 0 deletions lib/std/Io/Reader/Limited.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited {

fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
const l: *Limited = @fieldParentPtr("interface", r);
if (l.remaining == .nothing) return error.EndOfStream;

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now reading 0 bytes is a no-op, not an error. It's like subtracting 0 from a number. I don't think that should change.

@rohlem rohlem Sep 2, 2025 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my bad, I confused the interface implementation function here (in Reader/Limited.zig) with the user-facing stream function in the interface Reader.zig.
Reader.stream already returns 0 if limit == .nothing was passed, in that case this function isn't called - all good then, sorry for the noise.
(We could document and assert(limit != .nothing); here in the implementation functions, but that's probably deemed unnecessary.)

const combined_limit = limit.min(l.remaining);
const n = try l.unlimited.stream(w, combined_limit);
l.remaining = l.remaining.subtract(n).?;
Expand All @@ -51,8 +52,51 @@ test stream {

fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
const l: *Limited = @fieldParentPtr("interface", r);
if (l.remaining == .nothing) return error.EndOfStream;
const combined_limit = limit.min(l.remaining);
const n = try l.unlimited.discard(combined_limit);
l.remaining = l.remaining.subtract(n).?;
return n;
}

test "end of stream, read, hit limit exactly" {
var f: Reader = .fixed("i'm dying");
var l = f.limited(.limited(4), &.{});
const r = &l.interface;

var buf: [2]u8 = undefined;
try r.readSliceAll(&buf);
try r.readSliceAll(&buf);
try std.testing.expectError(error.EndOfStream, l.interface.readSliceAll(&buf));
}

test "end of stream, read, hit limit after partial read" {
var f: Reader = .fixed("i'm dying");
var l = f.limited(.limited(5), &.{});
const r = &l.interface;

var buf: [2]u8 = undefined;
try r.readSliceAll(&buf);
try r.readSliceAll(&buf);
try std.testing.expectError(error.EndOfStream, l.interface.readSliceAll(&buf));
}

test "end of stream, discard, hit limit exactly" {
var f: Reader = .fixed("i'm dying");
var l = f.limited(.limited(4), &.{});
const r = &l.interface;

try r.discardAll(2);
try r.discardAll(2);
try std.testing.expectError(error.EndOfStream, l.interface.discardAll(2));
}

test "end of stream, discard, hit limit after partial read" {
var f: Reader = .fixed("i'm dying");
var l = f.limited(.limited(5), &.{});
const r = &l.interface;

try r.discardAll(2);
try r.discardAll(2);
try std.testing.expectError(error.EndOfStream, l.interface.discardAll(2));
}