• English
  • 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.

                            Source Stream (io.Reader)
    
    
                             ┌────────────────────┐
                             │  r.Read(buf[n:])   │◄─────────┐
                             └────────────────────┘          │
                                        │                    │
                          n += nn       │                    │
                                        ▼                    │
                             Is n >= min OR err != nil?      │
                               /                  \           │
                             No                   Yes         │
                              │                    │          │
                              └────────────────────┴──────────┘
    
    
                                     Evaluate Return Conditions:
                                     ├─ n >= min          ──► err = nil
                                     ├─ n == 0 && EOF     ──► err = io.EOF
                                     ├─ 0 < n < min && EOF ─► err = io.ErrUnexpectedEOF
                                     └─ n < min && err    ──► err = source error

    Key Characteristics

    io.ReadAtLeast:

    • Repeatedly calls r.Read until n >= min or an error occurs.
    • For min >= 0, a successful call (err == nil) guarantees n >= min.
    • Returns io.ErrShortBuffer immediately if len(buf) < min, without calling r.Read.
    • Returns (0, nil) when min <= 0, without performing any reads.
    • Converts io.EOF to io.ErrUnexpectedEOF when at least one byte has been read but fewer than min bytes were obtained.
    • Suppresses the final read error when the accumulated byte count reaches min.

    Core Semantics

    io.ReadAtLeast enforces a minimum, not an exact amount.

    If the accumulated byte count reaches min, the function returns n >= min and err = nil, even if the final underlying Read also returned an error.

    The underlying error is not deferred or returned by a later io.ReadAtLeast call. It is simply not returned from the call that already satisfied the minimum.

    n, err := io.ReadAtLeast(r, buf, min)

    Quick Start

    The most useful way to understand io.ReadAtLeast is to use a reader that deliberately performs short reads.

    package main
    
    import (
    	"fmt"
    	"io"
    )
    
    type chunkReader struct {
    	data []byte
    }
    
    func (r *chunkReader) Read(p []byte) (int, error) {
    	if len(r.data) == 0 {
    		return 0, io.EOF
    	}
    
    	n := min(2, len(r.data))
    	copy(p, r.data[:n])
    	r.data = r.data[n:]
    
    	return n, nil
    }
    
    func main() {
    	src := &chunkReader{data: []byte("Hello, Go World!")}
    	buf := make([]byte, 32)
    
    	const minBytes = 5
    
    	// chunkReader returns at most 2 bytes per Read.
    	// ReadAtLeast therefore needs three reads:
    	// 2 + 2 + 2 = 6 bytes.
    	n, err := io.ReadAtLeast(src, buf, minBytes)
    	if err != nil {
    		fmt.Printf("read failed: %v\n", err)
    		return
    	}
    
    	fmt.Printf("Successfully read %d bytes: %q\n", n, buf[:n])
    }

    Output:

    Successfully read 6 bytes: "Hello,"

    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:

    1. Validate that the buffer can hold at least min bytes.
    2. Repeatedly call Read until min bytes have been accumulated or an error occurs.
    3. Normalize the result according to the accumulated byte count.
    func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
    	if len(buf) < min {
    		return 0, ErrShortBuffer
    	}
    
    	for n < min && err == nil {
    		var nn int
    		nn, err = r.Read(buf[n:])
    		n += nn
    	}
    
    	if n >= min {
    		err = nil
    	} else if n > 0 && err == EOF {
    		err = ErrUnexpectedEOF
    	}
    
    	return
    }

    Return Conditions

    ConditionReturned nReturned errMeaning
    len(buf) < min0io.ErrShortBufferBuffer cannot satisfy the requested minimum. No read occurs.
    min <= 00nilMinimum is already satisfied. No read occurs.
    n >= minnnilMinimum was satisfied. Any error from the final Read is not returned.
    n == 0, source returns io.EOF0io.EOFSource ended before any bytes were read.
    0 < n < min, source returns io.EOFnio.ErrUnexpectedEOFSource ended before the required minimum was obtained.
    n < min, source returns another errornsource errorThe underlying error is preserved.

    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:

    func ReadFull(r Reader, buf []byte) (n int, err error) {
    	return ReadAtLeast(r, buf, len(buf))
    }

    The practical difference is:

    APISemantics
    io.ReadFullFill the supplied buffer completely.
    io.ReadAtLeastObtain at least min bytes in the supplied buffer.

    Use io.ReadFull when the protocol or format requires an exact number of bytes.

    var header [6]byte
    
    n, err := io.ReadFull(conn, header[:])

    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.

    buf := make([]byte, 128)
    
    n, err := io.ReadAtLeast(conn, buf, 6)

    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:

    +----------------+----------------------+
    | Magic (2 bytes)| Payload Length (4 B) |
    +----------------+----------------------+

    A tempting implementation is:

    buf := make([]byte, 128)
    
    n, err := io.ReadAtLeast(conn, buf, 6)
    if err != nil {
    	return err
    }
    
    // Parse only buf[:6].

    This is potentially dangerous.

    The underlying conn.Read is allowed to fill more than 6 bytes. If it returns, for example, 100 bytes, then:

    buf[:6]    -> protocol header
    buf[6:100] -> already-read payload

    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:

    package example
    
    import (
    	"encoding/binary"
    	"fmt"
    	"io"
    	"net"
    )
    
    const headerLen = 6
    
    func ReadPacketHeader(conn net.Conn) (uint32, error) {
    	var buf [headerLen]byte
    
    	if _, err := io.ReadFull(conn, buf[:]); err != nil {
    		if err == io.EOF {
    			return 0, fmt.Errorf("connection closed before packet header")
    		}
    		if err == io.ErrUnexpectedEOF {
    			return 0, fmt.Errorf("connection closed while reading packet header")
    		}
    		return 0, fmt.Errorf("read packet header: %w", err)
    	}
    
    	magic := binary.BigEndian.Uint16(buf[0:2])
    	if magic != 0xFAFA {
    		return 0, fmt.Errorf("invalid magic header: 0x%X", magic)
    	}
    
    	payloadLen := binary.BigEndian.Uint32(buf[2:6])
    	return payloadLen, nil
    }

    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:

    +----------------+--------------------------+
    | Parsed Header  | Buffered Payload Bytes   |
    +----------------+--------------------------+
    
    
                 must retain

    The important rule is:

    Never use an oversized io.ReadAtLeast buffer 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.

    buf := make([]byte, 128)
    
    n, err := io.ReadAtLeast(conn, buf, 6)

    The call guarantees only:

    success => n >= 6

    It does not guarantee:

    success => n == 6

    The underlying Read may return more than min bytes in a single operation.

    If exactly six bytes are required, use:

    io.ReadFull(conn, buf[:6])

    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:

    n = 5
    err = io.EOF

    for a request that has room for at least five bytes.

    Then:

    n, err := io.ReadAtLeast(src, buf, 5)
    
    // n == 5
    // err == nil

    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:

    buf := make([]byte, 4)
    
    n, err := io.ReadAtLeast(r, buf, 8)
    
    // n == 0
    // err == io.ErrShortBuffer

    No read is performed.

    4. Distinguish io.EOF from io.ErrUnexpectedEOF

    When the minimum is not satisfied:

    • io.EOF means no bytes were obtained before the source ended.
    • io.ErrUnexpectedEOF means some bytes were obtained, but the source ended before reaching min.

    For protocol parsing, this distinction is often significant.

    n, err := io.ReadAtLeast(r, buf, min)
    if err != nil {
    	switch err {
    	case io.EOF:
    		// No bytes were available.
    	case io.ErrUnexpectedEOF:
    		// Partial data was received; the stream ended prematurely.
    	default:
    		// Underlying I/O failure.
    	}
    }

    5. io.ReadAtLeast Does Not Bound Blocking

    io.ReadAtLeast may perform multiple reads.

    Therefore:

    io.ReadAtLeast(conn, buf, 1024)

    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 N bytes 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

    RequirementRecommended ChoiceRationale
    Read an exact fixed number of bytesio.ReadFull(r, buf)Fills the buffer or reports io.EOF / io.ErrUnexpectedEOF.
    Read at least N bytes into a larger bufferio.ReadAtLeast(r, buf, min)Enforces a minimum while allowing the underlying reader to return more.
    Read up to a maximum number of bytesio.LimitReader(r, limit)Converts a byte limit into a bounded io.Reader.
    Copy at most N bytesio.CopyN(dst, src, n)Transfers a bounded number of bytes to a destination.
    Perform one opportunistic readr.Read(buf)Lets the underlying reader return whatever amount is currently available.

    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.