{"record":{"id":"13891c8aff70486a","repo":"jmoiron/sqlx","slug":"must-pass-a-pointer-not-a-value-to-structscan-de","errorCode":null,"errorMessage":"must pass a pointer, not a value, to StructScan destination","messagePattern":"must pass a pointer, not a value, to StructScan destination","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sqlx.go","lineNumber":606,"sourceCode":"func (r *Rows) SliceScan() ([]interface{}, error) {\n\treturn SliceScan(r)\n}\n\n// MapScan using this Rows.\nfunc (r *Rows) MapScan(dest map[string]interface{}) error {\n\treturn MapScan(r, dest)\n}\n\n// StructScan is like sql.Rows.Scan, but scans a single Row into a single Struct.\n// Use this and iterate over Rows manually when the memory load of Select() might be\n// prohibitive.  *Rows.StructScan caches the reflect work of matching up column\n// positions to fields to avoid that overhead per scan, which means it is not safe\n// to run StructScan on the same Rows instance with different struct types.\nfunc (r *Rows) StructScan(dest interface{}) error {\n\tv := reflect.ValueOf(dest)\n\n\tif v.Kind() != reflect.Ptr {\n\t\treturn errors.New(\"must pass a pointer, not a value, to StructScan destination\")\n\t}\n\n\tv = v.Elem()\n\n\tif !r.started {\n\t\tcolumns, err := r.Columns()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tm := r.Mapper\n\n\t\tr.fields = m.TraversalsByName(v.Type(), columns)\n\t\t// if we are not unsafe and are missing fields, return an error\n\t\tif f, err := missingFields(r.fields); err != nil && !r.unsafe {\n\t\t\treturn fmt.Errorf(\"missing destination name %s in %T\", columns[f], dest)\n\t\t}\n\t\tr.values = make([]interface{}, len(columns))\n\t\tr.started = true","sourceCodeStart":588,"sourceCodeEnd":624,"githubUrl":"https://github.com/jmoiron/sqlx/blob/41dac167fdad5e3fd81d66cafba0951dc6823a30/sqlx.go#L588-L624","documentation":"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.","triggerScenarios":"Calling rows.StructScan(s) where s is a struct value instead of &s, e.g. for rows.Next() { rows.StructScan(User{}) }.","commonSituations":"Forgetting the & when refactoring from Scan(&vars...) style to StructScan; passing a struct literal directly; wrapping StructScan in a helper that drops addressability.","solutions":["Pass the address of your struct: rows.StructScan(&dest)","Declare the loop variable as a value and take its address each iteration","Ensure wrapper functions accept interface{} but callers still pass pointers","Add a helper that returns an explicit error message reminding about pointers"],"exampleFix":"// before\nfor rows.Next() {\n    rows.StructScan(u)\n}\n// after\nfor rows.Next() {\n    var u User\n    rows.StructScan(&u)\n}","handlingStrategy":"validation","validationCode":"func safeStructScan(rows *sqlx.Rows, dest interface{}) error {\n    if reflect.ValueOf(dest).Kind() != reflect.Ptr {\n        return fmt.Errorf(\"StructScan needs &dest, got %T\", dest)\n    }\n    return rows.StructScan(dest)\n}","typeGuard":"func isPointerDest(dest interface{}) bool {\n    return reflect.ValueOf(dest).Kind() == reflect.Ptr\n}","tryCatchPattern":"var u User\nif err := rows.StructScan(&u); err != nil {\n    if strings.Contains(err.Error(), \"must pass a pointer\") {\n        return fmt.Errorf(\"caller bug: pass &User{}: %w\", err)\n    }\n    return err\n}","preventionTips":["Always write rows.StructScan(&dest) with the ampersand","Never pass struct literals directly to StructScan","Add a vet-style lint or wrapper that asserts pointer destinations"],"tags":["go","sqlx","structscan","reflection"],"backgroundTag":"non-pointer-scan-destination","analyzedSha":"41dac167fdad5e3fd81d66cafba0951dc6823a30","analyzedAt":"2026-09-03T06:10:20.382Z","contentChangedAt":"2026-09-03T06:10:20.382Z","schemaVersion":2},"datasetVersion":"2026-09-10T12:17:11.382Z"}