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

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

    The important part is at least.

    If the reader returns more than min bytes, those bytes are also consumed.

    io.ReadAtLeast guarantees 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:

    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)
    
    	n, err := io.ReadAtLeast(src, buf, 5)
    	if err != nil {
    		fmt.Printf("read failed: %v\n", err)
    		return
    	}
    
    	fmt.Printf("%d bytes: %q\n", n, buf[:n])
    }

    Output:

    6 bytes: "Hello,"

    The minimum was 5 bytes, but n is 6.

    Why? The reader returned:

    Read 1: 2 bytes
    Read 2: 2 bytes
    Read 3: 2 bytes
                 └── minimum of 5 reached

    io.ReadAtLeast does not trim the final read down to exactly min.

    The Core Semantics

    Conceptually, io.ReadAtLeast does this:

                     io.Reader
                         │
                         ▼
                  ┌─────────────┐
                  │    Read     │
                  └──────┬──────┘
                         │
                         ▼
                    n >= min?
                    /       \
                  no         yes
                   │          │
                   ▼          ▼
                 Read       success
                   │
                   └───────► ...

    The important rules are:

    SituationResult
    len(buf) < minio.ErrShortBuffer, no read
    min <= 0(0, nil), no read
    n >= minsuccess, err == nil
    source returns io.EOF before any byteio.EOF
    source returns io.EOF after some bytes but before minio.ErrUnexpectedEOF
    another error occurs before minthat error is returned

    If the minimum is reached, the final read error is suppressed:

    n, err := io.ReadAtLeast(r, buf, 5)
    
    // If the accumulated bytes reach 5:
    // n >= 5
    // err == nil

    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:

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

    The practical difference is simple:

    RequirementUse
    Read exactly enough to fill this bufferio.ReadFull
    Read at least N bytes into this bufferio.ReadAtLeast

    For example, a fixed six-byte header should normally use:

    var header [6]byte
    
    if _, err := io.ReadFull(conn, header[:]); err != nil {
    	return err
    }

    Not:

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

    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:

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

    This looks reasonable:

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

    But the call may return:

    n = 100

    Now:

    buf[:6]    → header
    buf[6:100] → payload bytes

    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:

    var header [6]byte
    
    if _, err := io.ReadFull(conn, header[:]); err != nil {
    	return fmt.Errorf("read header: %w", err)
    }

    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:

    TCP stream
    ─────────────────────────────────────────────►
    
    ┌──────────┬────────────────────────────────┐
    │  Header  │       Payload bytes            │
    └──────────┴────────────────────────────────┘
               ▲
               │
           must be retained
           by the buffer

    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

    Read → 0 bytes, io.EOF

    Result:

    n == 0
    err == io.EOF

    Source ends after partial data

    Read → 5 bytes, io.EOF

    Result:

    n == 5
    err == io.ErrUnexpectedEOF

    The distinction is useful for stream protocols:

    n, err := io.ReadAtLeast(r, buf, 8)
    if err != nil {
    	switch err {
    	case io.EOF:
    		// The stream ended before any data arrived.
    	case io.ErrUnexpectedEOF:
    		// Partial data arrived, but the required minimum was not reached.
    	default:
    		// Underlying I/O error.
    	}
    }

    If the minimum is reached, the operation succeeds even if the final Read also reports an error:

    // Reader returns:
    n = 8
    err = io.EOF
    
    // ReadAtLeast returns:
    n = 8
    err = nil

    The minimum requirement has already been satisfied.

    Common Mistakes

    Mistake 1: Treating min as an exact size

    Wrong assumption:

    n, err := io.ReadAtLeast(conn, buf, 6)
    
    // n == 6

    The actual guarantee is:

    success => n >= 6

    If you need exactly six bytes:

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

    Mistake 2: Using a Large Buffer Without Handling Extra Bytes

    This is dangerous:

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

    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:

    0 bytes + EOF
            → io.EOF
    
    some bytes + EOF before minimum
            → io.ErrUnexpectedEOF

    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:

    io.ReadAtLeast(conn, buf, 1024)

    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:

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

    the useful data is:

    buf[:n]

    not:

    buf[:min]

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

    RequirementChoice
    Read exactly len(buf) bytesio.ReadFull
    Read at least N bytesio.ReadAtLeast
    Read no more than N bytesio.LimitReader
    Copy at most N bytesio.CopyN
    Perform one normal readr.Read

    Rule of Thumb

    Use io.ReadAtLeast when 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?”