{"record":{"id":"d5340911a8492d2f","repo":"redis/go-redis","slug":"redis-scan-non-pointer-t","errorCode":null,"errorMessage":"redis.Scan(non-pointer %T)","messagePattern":"redis\\.Scan\\(non-pointer %T\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/hscan/hscan.go","lineNumber":60,"sourceCode":"\t\treflect.Ptr:           decodeUnsupported,\n\t\treflect.Slice:         decodeSlice,\n\t\treflect.String:        decodeString,\n\t\treflect.Struct:        decodeUnsupported,\n\t\treflect.UnsafePointer: decodeUnsupported,\n\t}\n\n\t// Global map of struct field specs that is populated once for every new\n\t// struct type that is scanned. This caches the field types and the corresponding\n\t// decoder functions to avoid iterating through struct fields on subsequent scans.\n\tglobalStructMap = newStructMap()\n)\n\nfunc Struct(dst interface{}) (StructValue, error) {\n\tv := reflect.ValueOf(dst)\n\n\t// The destination to scan into should be a struct pointer.\n\tif v.Kind() != reflect.Ptr || v.IsNil() {\n\t\treturn StructValue{}, fmt.Errorf(\"redis.Scan(non-pointer %T)\", dst)\n\t}\n\n\tv = v.Elem()\n\tif v.Kind() != reflect.Struct {\n\t\treturn StructValue{}, fmt.Errorf(\"redis.Scan(non-struct %T)\", dst)\n\t}\n\n\treturn StructValue{\n\t\tspec:  globalStructMap.get(v.Type()),\n\t\tvalue: v,\n\t}, nil\n}\n\n// Scan scans the results from a key-value Redis map result set to a destination struct.\n// The Redis keys are matched to the struct's field with the `redis` tag.\nfunc Scan(dst interface{}, keys []interface{}, vals []interface{}) error {\n\tif len(keys) != len(vals) {\n\t\treturn errors.New(\"args should have the same number of keys and vals\")","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/redis/go-redis/blob/c5cad058c72f58370553b48566302303cf8a2e89/internal/hscan/hscan.go#L42-L78","documentation":"hscan.Struct (exposed as redis.Struct, used by HGETALL-based struct scanning) requires the destination to be a non-nil pointer to a struct. If dst is not a pointer or is a nil pointer, Struct returns 'redis.Scan(non-pointer %T)'. This is a caller-side type misuse detected via reflection before any Redis data is touched.","triggerScenarios":"redis.Scan(myStruct) instead of redis.Scan(&myStruct); passing a nil typed pointer (*MyStruct)(nil); passing a map or slice address where a struct pointer is required.","commonSituations":"Copying examples and forgetting the &; storing the destination in an interface{} that holds a non-pointer; scanning into a map[string]string instead of a struct.","solutions":["Pass a pointer to a struct: redis.Scan(&user)","Ensure the pointer is non-nil and initialized","Verify the pointed-to type is a struct, not a map/slice","When dst is interface{}, assert it holds a *T before calling Scan"],"exampleFix":"// before\nvar user User\nres, err := redis.Scan(user) // redis.Scan(non-pointer hscan.User)\n// after\nvar user User\nres, err := redis.Scan(&user)","handlingStrategy":"validation","validationCode":"func validScanDst(dst interface{}) bool {\n\tv := reflect.ValueOf(dst)\n\treturn v.Kind() == reflect.Ptr && !v.IsNil() && v.Elem().Kind() == reflect.Struct\n}","typeGuard":null,"tryCatchPattern":"res, err := redis.Scan(dst)\nif err != nil {\n\tif strings.Contains(err.Error(), \"non-pointer\") {\n\t\treturn fmt.Errorf(\"Scan requires &struct: %w\", err)\n\t}\n}","preventionTips":["Always pass &myStruct to redis.Scan","Check interfaces holding destinations for pointer semantics","Prefer typed function parameters (*User) over interface{} for scan helpers"],"tags":["reflection","struct-scan","api-misuse"],"backgroundTag":"scan-non-struct-destination","analyzedSha":"c5cad058c72f58370553b48566302303cf8a2e89","analyzedAt":"2026-09-01T06:50:53.388Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}