Skip to content

feat: Buffer readDiscard - #7244

Open
turuslan wants to merge 5 commits into
denoland:mainfrom
turuslan:feature/buffer-read-discard
Open

feat: Buffer readDiscard#7244
turuslan wants to merge 5 commits into
denoland:mainfrom
turuslan:feature/buffer-read-discard

Conversation

@turuslan

@turuslan turuslan commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Buffer class is used by parsers.
Sometimes parser needs to advance read position without copying bytes.
For example, zero-copy parser peeks available bytes(), then consumes some of them and advances read position.

Buffer class hides read position as #off private field.
The only function that advances read position is read(p)/readSync(p), which accept buffer as argument.

// currently available workaround
function readDiscard(buf: Buffer, n: number) {
  buf.readSync({ byteLength: n, set() {} });
}

Go provides https://pkg.go.dev/bytes#Buffer.Next function, which advances read position without copying bytes.
This PR proposes readDiscard(n) (please suggest better name), which doesn't return value.

There is truncate(n) function, which does opposite, it keeps "first N bytes", not "after N bytes"

Signed-off-by: turuslan <turuslan.devbox@gmail.com>
@github-actions github-actions Bot added the io label Jul 20, 2026
@turuslan turuslan changed the title Buffer readDiscard feat Buffer readDiscard Jul 20, 2026
@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.03%. Comparing base (99cc1a3) to head (fab3a7e).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7244   +/-   ##
=======================================
  Coverage   95.02%   95.03%           
=======================================
  Files         618      619    +1     
  Lines       51496    51506   +10     
  Branches     9300     9301    +1     
=======================================
+ Hits        48936    48947   +11     
  Misses       2021     2021           
+ Partials      539      538    -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@turuslan turuslan changed the title feat Buffer readDiscard feat: Buffer readDiscard Jul 20, 2026
turuslan added 2 commits July 20, 2026 09:03
Signed-off-by: turuslan <turuslan.devbox@gmail.com>
Signed-off-by: turuslan <turuslan.devbox@gmail.com>

@bartlomieju bartlomieju left a comment

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.

Thanks for the PR, and for the clear motivation — the buf.readSync({ byteLength: n, set() {} }) workaround is genuinely awkward, and a bytes.Buffer.Next(n)-style consume is a reasonable thing to want.

The blocker isn't the code, it's the venue. @std/io is in wind-down: io/mod.ts states that the Reader/Writer interfaces are deprecated in Deno and that these utilities are deprecated along with them, and the module's recent history is almost entirely removals (BufReader, BufWriter, readDelim, MultiReader). Adding public surface to a module we're winding down is a policy call, so I'd like a second maintainer opinion before we take this. Please don't take that as a rejection — it's a question about @std/io rather than about your change.

If we do take it, I've left inline notes on a few things that should be fixed first. The NaN one is the important one — it silently corrupts the buffer.

One more, on naming: readDiscard doesn't match anything else on the class (truncate, reset, bytes, empty). Since you invited suggestions — discard(n) or skip(n) reads better to me.

Comment thread io/buffer.ts Outdated
* @param n The number of bytes to discard.
*/
readDiscard(n: number) {
if (n < 0 || n > this.length) {

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 guard misses non-integers. readDiscard(NaN) passes both comparisons — NaN < 0 and NaN > this.length are both false — so #off becomes NaN permanently, which makes length NaN and silently corrupts the buffer with no error. Same for fractional n.

Suggested change
if (n < 0 || n > this.length) {
if (!Number.isInteger(n) || n < 0 || n > this.length) {

Comment thread io/buffer.ts Outdated
*/
readDiscard(n: number) {
if (n < 0 || n > this.length) {
throw new Error("Buffer read discard out of range");

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.

Two things here, per the error-message style guide in CONTRIBUTING.md:

  1. The message is passive and state-based rather than action-based, and it omits the offending value — the reader can't tell what n was or what range was expected.
  2. RangeError is the right type for an out-of-range argument. (truncate() above uses bare Error, but that's pre-existing and new code shouldn't copy it.)
Suggested change
throw new Error("Buffer read discard out of range");
throw new RangeError(
`Cannot discard bytes as "n" must be an integer >= 0 and <= ${this.length}: received ${n}`,
);

Comment thread io/buffer.ts
* assertEquals(buf.length, 10);
* ```
*
* @param n The number of bytes to discard.

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.

The prose above documents that this throws, so it should carry a @throws tag to match the rest of the module:

Suggested change
* @param n The number of bytes to discard.
* @param n The number of bytes to discard.
* @throws {RangeError} If `n` is not an integer, is negative, or exceeds the buffer length.

Comment thread io/buffer_test.ts Outdated
writeAllSync(reader, bytes);
assertEquals(reader.bytes(), bytes.subarray(0, 4));

assertThrows(() => reader.readDiscard(5));

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 asserts that something throws, but pins neither the error class nor the message — so it would still pass if the failure mode changed entirely. Repo tests normally pin both, e.g. assertThrows(() => reader.readDiscard(5), RangeError, "Cannot discard bytes").

Could you also cover the edge cases? Negative n, n === 0, discarding from an empty buffer, and a non-integer such as NaN (see my note on the guard — that one currently passes silently).

turuslan added 2 commits July 30, 2026 20:40
Signed-off-by: turuslan <turuslan.devbox@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants