jmoiron/sqlx · error

must pass a pointer, not a value, to StructScan destination

Error message

must pass a pointer, not a value, to StructScan destination

What it means

Rows.StructScan requires a pointer to a struct so it can write scanned column values into the caller's memory. Passing a non-pointer (a struct value) gives reflect a read-only value that cannot be assigned to, so sqlx rejects it with this error.

Source

Thrown at sqlx.go:606

func (r *Rows) SliceScan() ([]interface{}, error) {
	return SliceScan(r)
}

// MapScan using this Rows.
func (r *Rows) MapScan(dest map[string]interface{}) error {
	return MapScan(r, dest)
}

// StructScan is like sql.Rows.Scan, but scans a single Row into a single Struct.
// Use this and iterate over Rows manually when the memory load of Select() might be
// prohibitive.  *Rows.StructScan caches the reflect work of matching up column
// positions to fields to avoid that overhead per scan, which means it is not safe
// to run StructScan on the same Rows instance with different struct types.
func (r *Rows) StructScan(dest interface{}) error {
	v := reflect.ValueOf(dest)

	if v.Kind() != reflect.Ptr {
		return errors.New("must pass a pointer, not a value, to StructScan destination")
	}

	v = v.Elem()

	if !r.started {
		columns, err := r.Columns()
		if err != nil {
			return err
		}
		m := r.Mapper

		r.fields = m.TraversalsByName(v.Type(), columns)
		// if we are not unsafe and are missing fields, return an error
		if f, err := missingFields(r.fields); err != nil && !r.unsafe {
			return fmt.Errorf("missing destination name %s in %T", columns[f], dest)
		}
		r.values = make([]interface{}, len(columns))
		r.started = true

View on GitHub (pinned to 41dac167fd)

Solutions

  1. Pass the address of your struct: rows.StructScan(&dest)
  2. Declare the loop variable as a value and take its address each iteration
  3. Ensure wrapper functions accept interface{} but callers still pass pointers
  4. Add a helper that returns an explicit error message reminding about pointers

Example fix

// before
for rows.Next() {
    rows.StructScan(u)
}
// after
for rows.Next() {
    var u User
    rows.StructScan(&u)
}
Defensive patterns

Strategy: validation

Validate before calling

func safeStructScan(rows *sqlx.Rows, dest interface{}) error {
    if reflect.ValueOf(dest).Kind() != reflect.Ptr {
        return fmt.Errorf("StructScan needs &dest, got %T", dest)
    }
    return rows.StructScan(dest)
}

Type guard

func isPointerDest(dest interface{}) bool {
    return reflect.ValueOf(dest).Kind() == reflect.Ptr
}

Try / catch

var u User
if err := rows.StructScan(&u); err != nil {
    if strings.Contains(err.Error(), "must pass a pointer") {
        return fmt.Errorf("caller bug: pass &User{}: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling rows.StructScan(s) where s is a struct value instead of &s, e.g. for rows.Next() { rows.StructScan(User{}) }.

Common situations: Forgetting the & when refactoring from Scan(&vars...) style to StructScan; passing a struct literal directly; wrapping StructScan in a helper that drops addressability.

Related errors


AI-assisted analysis of jmoiron/sqlx@41dac167fd (2026-09-03). Data as JSON: /api/errors/13891c8aff70486a. Report an issue: GitHub.