{"record":{"id":"40ffb36dc04f45c7","repo":"redis/go-redis","slug":"redis-scan-non-struct-t","errorCode":null,"errorMessage":"redis.Scan(non-struct %T)","messagePattern":"redis\\.Scan\\(non-struct %T\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/hscan/hscan.go","lineNumber":65,"sourceCode":"\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\")\n\t}\n\n\tstrct, err := Struct(dst)\n\tif err != nil {\n\t\treturn err","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/redis/go-redis/blob/c5cad058c72f58370553b48566302303cf8a2e89/internal/hscan/hscan.go#L47-L83","documentation":"After dereferencing the destination pointer, hscan.Struct checks that the pointed-to value is actually a struct. If dst is a pointer to something else (map, slice, string, number), it returns 'redis.Scan(non-struct %T)'. Struct scanning via HGETALL hash fields only works with struct destinations.","triggerScenarios":"redis.Scan(&myMap), redis.Scan(&mySlice), or redis.Scan(&someString) — a valid non-nil pointer whose element is not a struct kind.","commonSituations":"Refactoring code from MapScan to StructScan and keeping a map destination; generics/interface{} plumbing that hides the real type; scanning HGETALL into map[string]interface{} via Scan.","solutions":["Change the destination to a struct whose fields map to the hash fields","Use r.MapScan(ctx, val) if you want a map result instead","Add struct tags (redis:\"field\") to control hash-field mapping"],"exampleFix":"// before\nm := map[string]string{}\nres, err := redis.Scan(&m) // redis.Scan(non-struct map[string]string)\n// after\ntype User struct {\n\tName string `redis:\"name\"`\n}\nu := User{}\nres, err := redis.Scan(&u)","handlingStrategy":"validation","validationCode":"func isStructPtr(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 && strings.Contains(err.Error(), \"non-struct\") {\n\treturn fmt.Errorf(\"use MapScan for map destinations: %w\", err)\n}","preventionTips":["Only scan into structs; use MapScan for maps","Document scan-helper signatures as accepting struct pointers","Add unit tests covering every destination type your helpers accept"],"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"}