valyala/fasthttp · error

must implement readat

Error message

must implement readat

What it means

This Read path on fs.go requires random-access reads, so it type-asserts the underlying file to io.ReaderAt. When ff.f does not implement ReadAt, it returns 'must implement readat'. The library cannot serve positional reads without ReadAt support.

Solutions

  1. Implement io.ReaderAt on the type stored in ff.f (back it with a real file, buffer, or memory)
  2. Use an *os.File or bytes.Reader, which already implement ReaderAt
  3. Serve non-ReaderAt sources via a plain io.Copy in your own handler instead of fasthttp's fs file cache
  4. Check that the file wasn't replaced by a streaming (compressed/decompressed) reader lacking ReadAt

Example fix

// before
func (f *streamFile) Read(p []byte) (int, error) { return f.rc.Read(p) }
// after
func (f *streamFile) ReadAt(p []byte, off int64) (int, error) {
    // implement positional read, e.g. seek to off on a temp file
    return f.tmp.ReadAt(p, off)
}
Defensive patterns

Strategy: type-guard

Validate before calling

if _, ok := ff.f.(io.ReaderAt); !ok {
    return errors.New("underlying file must implement io.ReaderAt for this read path")
}

Type guard

func isReaderAt(f any) bool {
    _, ok := f.(io.ReaderAt)
    return ok
}

Try / catch

n, err := file.Read(buf)
if err != nil && strings.Contains(err.Error(), "must implement readat") {
    // fall back to a sequential io.Copy-based handler
}

Prevention

When it happens

Trigger: Calling the Read method at fs.go:845 (a startPos-tracking reader around fsFile) when the underlying ff.f is a non-ReaderAt file, such as a streaming reader or custom fs.File implementation.

Common situations: Custom fs.File wrappers (e.g. wrapping an S3 stream, pipe, or net.Conn) passed to fasthttp's request handler serving static files; content streamed from a remote source instead of an *os.File.

Related errors


AI-assisted analysis of valyala/fasthttp@c96f600972 (2026-08-31). Data as JSON: /api/errors/e9f5d58ac5ccb339. Report an issue: GitHub.

Appendix: source

Thrown at fs.go:845

	r.startPos = startPos
	r.endPos = endPos + 1
	return nil
}

func (r *fsSmallFileReader) Read(p []byte) (int, error) {
	tailLen := r.endPos - r.startPos
	if tailLen <= 0 {
		return 0, io.EOF
	}
	if len(p) > tailLen {
		p = p[:tailLen]
	}

	ff := r.ff
	if ff.f != nil {
		ra, ok := ff.f.(io.ReaderAt)
		if !ok {
			return 0, errors.New("must implement readat")
		}
		n, err := ra.ReadAt(p, int64(r.startPos))
		r.startPos += n
		return n, err
	}

	n := copy(p, ff.dirIndex[r.startPos:])
	r.startPos += n
	return n, nil
}

func (r *fsSmallFileReader) WriteTo(w io.Writer) (int64, error) {
	ff := r.ff

	var n int
	var err error
	if ff.f == nil {
		n, err = w.Write(ff.dirIndex[r.startPos:r.endPos])

View on GitHub (pinned to c96f600972)