Example: io.ReadAtLeast
io.ReadAtLeast reads from an io.Reader into a buffer until it has read at least min bytes or encounters an error.
It addresses a fundamental property of io.Reader: a single Read call may return fewer bytes than requested. io.ReadAtLeast repeatedly calls Read until the minimum threshold is satisfied or the source cannot provide more data.
Key Characteristics
io.ReadAtLeast:
- Repeatedly calls
r.Readuntiln >= minor an error occurs. - For
min >= 0, a successful call (err == nil) guaranteesn >= min. - Returns
io.ErrShortBufferimmediately iflen(buf) < min, without callingr.Read. - Returns
(0, nil)whenmin <= 0, without performing any reads. - Converts
io.EOFtoio.ErrUnexpectedEOFwhen at least one byte has been read but fewer thanminbytes were obtained. - Suppresses the final read error when the accumulated byte count reaches
min.
Core Semantics
io.ReadAtLeastenforces a minimum, not an exact amount.If the accumulated byte count reaches
min, the function returnsn >= minanderr = nil, even if the final underlyingReadalso returned an error.The underlying error is not deferred or returned by a later
io.ReadAtLeastcall. It is simply not returned from the call that already satisfied the minimum.
Quick Start
The most useful way to understand io.ReadAtLeast is to use a reader that deliberately performs short reads.
Output:
The important detail is that n is 6, not 5.
min specifies the minimum amount required for success. It does not tell io.ReadAtLeast to stop at exactly min bytes.
Implementation and Error Semantics
The implementation has three important stages:
- Validate that the buffer can hold at least
minbytes. - Repeatedly call
Readuntilminbytes have been accumulated or an error occurs. - Normalize the result according to the accumulated byte count.
Return Conditions
No-Progress Readers
io.ReadAtLeast does not impose a timeout or a maximum number of zero-byte reads.
A broken or unusual io.Reader that repeatedly returns (0, nil) can therefore cause io.ReadAtLeast to continue indefinitely.
For network operations, io.ReadAtLeast is not a timeout or cancellation mechanism. The underlying connection must provide appropriate deadlines or cancellation behavior.
io.ReadAtLeast vs. io.ReadFull
io.ReadFull is a convenience wrapper around io.ReadAtLeast:
The practical difference is:
Use io.ReadFull when the protocol or format requires an exact number of bytes.
Use io.ReadAtLeast when the buffer is intentionally larger than the minimum required amount and the caller is prepared to handle any bytes beyond min.
The second example does not mean “read exactly 6 bytes.”
The underlying Read receives buf[n:], which may have capacity for substantially more data.
Production Use Case: Protocol Headers and Over-Reading
This distinction matters when parsing stream protocols such as TCP framing.
Suppose a protocol uses a 6-byte header:
A tempting implementation is:
This is potentially dangerous.
The underlying conn.Read is allowed to fill more than 6 bytes. If it returns, for example, 100 bytes, then:
If the function parses the first six bytes and then discards the buffer, the remaining 94 bytes have already been consumed from the connection and are no longer available to the next parser.
Safer Approach for Fixed Headers
When only the header is required, use an exactly sized buffer with io.ReadFull:
If an implementation intentionally pre-buffers additional payload bytes with io.ReadAtLeast, those extra bytes must become part of the stream-reader state and remain available to subsequent parsing.
For example, a buffered protocol reader may retain:
The important rule is:
Never use an oversized
io.ReadAtLeastbuffer unless the architecture explicitly owns the bytes beyond the minimum.
Critical Production Pitfalls
1. min Is Not an Exact Read Size
This is the most important semantic distinction.
The call guarantees only:
It does not guarantee:
The underlying Read may return more than min bytes in a single operation.
If exactly six bytes are required, use:
2. Terminal Error Suppression When n >= min
If the final Read returns enough bytes to satisfy min together with an error, io.ReadAtLeast returns success.
For example, a reader may legally return:
for a request that has room for at least five bytes.
Then:
The io.EOF is not returned by io.ReadAtLeast.
This behavior is intentional: the minimum requirement has already been satisfied.
3. min > len(buf) Returns io.ErrShortBuffer
The buffer must be large enough to hold the requested minimum:
No read is performed.
4. Distinguish io.EOF from io.ErrUnexpectedEOF
When the minimum is not satisfied:
io.EOFmeans no bytes were obtained before the source ended.io.ErrUnexpectedEOFmeans some bytes were obtained, but the source ended before reachingmin.
For protocol parsing, this distinction is often significant.
5. io.ReadAtLeast Does Not Bound Blocking
io.ReadAtLeast may perform multiple reads.
Therefore:
can remain blocked while waiting for enough bytes to arrive.
For network code, pair the operation with appropriate connection deadlines or another cancellation mechanism.
io.ReadAtLeast solves short reads. It does not solve slow or stalled sources.
When io.ReadAtLeast Is the Right Abstraction
io.ReadAtLeast is useful when the caller has explicitly designed around a minimum threshold.
Typical examples include:
- Parsing a variable-size prefix where at least
Nbytes are required before decoding. - Filling a protocol staging buffer to a minimum size.
- Reading a format whose parser can operate once a minimum prefix is available.
- Implementing higher-level readers that need a minimum amount of buffered data.
It is usually not the best choice for a fixed-size protocol header. In that case, io.ReadFull communicates the requirement more precisely and avoids accidental over-reading.
Decision Matrix
Production Rule
When choosing between io.ReadFull and io.ReadAtLeast, ask one question:
Do I need exactly this many bytes, or merely at least this many bytes?
If the answer is exactly, use io.ReadFull.
If the answer is at least, use io.ReadAtLeast—but make sure your design intentionally handles any bytes beyond the minimum.