jmoiron/sqlx · error

argument not a struct

Error message

argument not a struct

What it means

fieldsByTraversal is used by StructScan-style helpers in sqlx to map database columns onto fields of a struct via reflectx traversals. It indirects the destination value and requires it to be (a pointer to) a struct; if the underlying kind is not struct it cannot receive the scanned fields, so it returns this error. The library throws it because column-to-field mapping is only meaningful for structs.

Source

Thrown at sqlx.go:1029

func baseType(t reflect.Type, expected reflect.Kind) (reflect.Type, error) {
	t = reflectx.Deref(t)
	if t.Kind() != expected {
		return nil, fmt.Errorf("expected %s but got %s", expected, t.Kind())
	}
	return t, nil
}

// fieldsByName fills a values interface with fields from the passed value based
// on the traversals in int.  If ptrs is true, return addresses instead of values.
// We write this instead of using FieldsByName to save allocations and map lookups
// when iterating over many rows.  Empty traversals will get an interface pointer.
// Because of the necessity of requesting ptrs or values, it's considered a bit too
// specialized for inclusion in reflectx itself.
func fieldsByTraversal(v reflect.Value, traversals [][]int, values []interface{}, ptrs bool) error {
	v = reflect.Indirect(v)
	if v.Kind() != reflect.Struct {
		return errors.New("argument not a struct")
	}

	for i, traversal := range traversals {
		if len(traversal) == 0 {
			values[i] = new(interface{})
			continue
		}
		f := reflectx.FieldByIndexes(v, traversal)
		if ptrs {
			values[i] = f.Addr().Interface()
		} else {
			values[i] = f.Interface()
		}
	}
	return nil
}

func missingFields(transversals [][]int) (field int, err error) {

View on GitHub (pinned to 41dac167fd)

Solutions

  1. Ensure the destination passed to StructScan is a *struct (or pointer to slice of structs for Select).
  2. Use Get/Select/ScanRow for scalar or slice-of-scalar results instead of StructScan.
  3. Check that the pointer is non-nil before calling StructScan.
  4. Verify the reflect.Value being scanned actually points at a struct kind.

Example fix

// before
var count int
rows.StructScan(&count)
// after
var row struct{ Count int }
rows.StructScan(&row)
Defensive patterns

Strategy: type-guard

Validate before calling

func isStructPtr(v interface{}) bool {
	r := reflect.Indirect(reflect.ValueOf(v))
	return r.Kind() == reflect.Struct && r.CanAddr()
}
// if !isStructPtr(dest) { use Get/Scan instead of StructScan }

Type guard

func asStructPtr(v interface{}) (bool, string) {
	r := reflect.Indirect(reflect.ValueOf(v))
	if r.Kind() != reflect.Struct {
		return false, fmt.Sprintf("got %s, want struct", r.Kind())
	}
	return true, ""
}

Try / catch

var row MyStruct
if err := rows.StructScan(&row); err != nil {
	if err.Error() == "argument not a struct" {
		// fall back to rows.Scan(&scalar) for non-struct destinations
	}
	return err
}

Prevention

When it happens

Trigger: Calling StructScan/Scan with a destination that is a pointer to a non-struct (e.g. *int, *[]string) or a nil pointer that reflects to a non-struct after Indirect, while the query returned columns (traversals were computed for a struct).

Common situations: Scanning a single-column result into *int via StructScan instead of db.Get(&intVar); passing **T where T is not a struct; passing an unaddressable value like map[string]interface{}; accidentally using StructScan on Rows for scalar queries.

Related errors


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