Example: io.ReadAtLeast
A single io.Reader.Read is allowed to return fewer bytes than the buffer can hold.
When your code needs at least N bytes, one Read is not enough. io.ReadAtLeast keeps reading until the minimum is satisfied or the source returns an error.
The important part is at least.
If the reader returns more than min bytes, those bytes are also consumed.
io.ReadAtLeastguarantees a minimum, not an exact amount.
That distinction matters when reading from a stream such as a TCP connection.
Quick Example
This reader deliberately returns at most two bytes per call:
Output:
The minimum was 5 bytes, but n is 6.
Why? The reader returned:
io.ReadAtLeast does not trim the final read down to exactly min.
The Core Semantics
Conceptually, io.ReadAtLeast does this:
The important rules are:
If the minimum is reached, the final read error is suppressed:
This follows the semantics of the operation: the requested minimum has been satisfied.
io.ReadAtLeast vs. io.ReadFull
io.ReadFull is essentially the exact-size version:
The practical difference is simple:
For example, a fixed six-byte header should normally use:
Not:
The second version allows the underlying Read to consume more than six bytes.
The Important Trap: Over-Reading
Consider a TCP protocol with a six-byte header:
This looks reasonable:
But the call may return:
Now:
Those payload bytes have already been consumed from conn.
If the code throws away buf[6:n], the next parser will never see them.
This is one of the most important things to understand about io.ReadAtLeast:
The minimum controls when the operation may finish. It does not control how many bytes the underlying reader may consume.
Safe Fixed-Size Header
When exactly six bytes are required, make the destination exactly six bytes:
Now the underlying Read cannot place more than six bytes into the supplied buffer.
When Over-Reading Is Intentional
Over-reading is not inherently wrong.
A buffered protocol reader may deliberately read a larger block:
If the parser owns a buffered reader and preserves those extra bytes for subsequent parsing, reading ahead can be useful.
The problem is not over-reading itself.
The problem is over-reading without owning the extra bytes.
Error Semantics
Suppose the caller needs at least 8 bytes.
Source ends immediately
Result:
Source ends after partial data
Result:
The distinction is useful for stream protocols:
If the minimum is reached, the operation succeeds even if the final Read also reports an error:
The minimum requirement has already been satisfied.
Common Mistakes
Mistake 1: Treating min as an exact size
Wrong assumption:
The actual guarantee is:
If you need exactly six bytes:
Mistake 2: Using a Large Buffer Without Handling Extra Bytes
This is dangerous:
If n > 6, the bytes after the first six belong to the stream too.
Either retain them in a buffer or use an exact-size destination.
Mistake 3: Confusing io.EOF and io.ErrUnexpectedEOF
These are different cases:
For protocol parsing, partial input is often a malformed or truncated message rather than a normal end-of-stream.
Mistake 4: Expecting ReadAtLeast to Provide a Timeout
This:
may block while waiting for enough bytes.
io.ReadAtLeast solves short reads.
It does not solve slow or stalled sources.
For network connections, use the connection's deadline/cancellation mechanisms separately.
Mistake 5: Ignoring the Byte Count
Even on success:
the useful data is:
not:
The reader may have supplied more than the minimum.
When to Use It
io.ReadAtLeast is a good fit when your algorithm genuinely needs a minimum amount of data before it can continue.
Typical cases include:
- A parser needs a minimum prefix before decoding.
- A staging buffer must contain at least
Nbytes. - A higher-level reader needs to accumulate enough data before processing.
- The caller intentionally owns and preserves bytes beyond the minimum.
For fixed-size structures, io.ReadFull is usually clearer.
Decision Matrix
Rule of Thumb
Use
io.ReadAtLeastwhen you need a minimum number of bytes and intentionally own whatever arrives beyond that minimum.
If you need an exact number of bytes, use io.ReadFull.
The key question is not:
“How many bytes do I want?”
It is:
“Am I prepared to handle bytes beyond the minimum?”