{"record":{"id":"2ebf5cf9e90593e0","repo":"apache/beam","slug":"value-v-must-be-ptr-to-struct","errorCode":null,"errorMessage":"value %v must be ptr to struct","messagePattern":"value (.+?) must be ptr to struct","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/go/pkg/beam/core/graph/fn.go","lineNumber":102,"sourceCode":"\t\tf, err := funcx.New(gen.Gen(gen.Name, gen.T, gen.Data))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &Fn{Fn: f, DynFn: gen}, nil\n\t}\n\n\tval := reflect.ValueOf(fn)\n\tswitch val.Type().Kind() {\n\tcase reflect.Func:\n\t\tf, err := funcx.New(reflectx.MakeFunc(fn))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &Fn{Fn: f}, nil\n\n\tcase reflect.Ptr:\n\t\tif val.Elem().Kind() != reflect.Struct {\n\t\t\treturn nil, errors.Errorf(\"value %v must be ptr to struct\", fn)\n\t\t}\n\n\t\t// Note that a ptr receiver is necessary if struct fields are updated in the\n\t\t// user code. Otherwise, updates are simply lost.\n\t\tfallthrough\n\n\tcase reflect.Struct:\n\t\tmethods := make(map[string]*funcx.Fn)\n\t\tannotations := make(map[string][]byte)\n\t\taf := reflect.Indirect(val).FieldByName(\"Annotations\")\n\t\tif af.IsValid() {\n\t\t\ta, ok := af.Interface().(map[string][]byte)\n\t\t\tif ok {\n\t\t\t\tannotations = a\n\t\t\t}\n\t\t}\n\t\tif methodsFuncs, ok := reflectx.WrapMethods(fn); ok {\n\t\t\tfor name, mfn := range methodsFuncs {","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/go/pkg/beam/core/graph/fn.go#L84-L120","documentation":"NewFn in fn.go accepts function values, function pointers, pointers to structs (DoFn/CombineFn receivers), and interfaces. When the value is a reflect.Ptr but the pointed-to value is not a struct, it throws \"value %v must be ptr to struct\". Struct-pointer receivers are required so user code can mutate DoFn state across elements; pointers to non-structs have no valid lifecycle methods to bind.","triggerScenarios":"Passing e.g. `&myInt`, `&slice`, or another pointer-to-non-struct to beam.ParDo/beam.Combine (which call NewFn), or a typed nil pointer to a non-struct.","commonSituations":"Typos like passing `&fn` (pointer to a func variable) instead of `fn`; passing a pointer to a map/slice created for config; mixing up a DoFn struct instance with a helper object.","solutions":["Pass the function value itself (e.g., `myFunc`) or a pointer to a struct implementing the DoFn/CombineFn methods.","If using a struct-based DoFn, define ProcessElement (and lifecycle) methods on it and pass `&myDoFn{...}`.","If passing a func, remove the `&` — pointers to funcs are not valid fn values.","Check the value's type with reflect before calling the beam API to fail fast with a clearer message."],"exampleFix":"// before\nn := 0\nbeam.ParDo(s, &n, coll) // ptr to non-struct\n// after\ntype adder struct{ n int }\nfunc (a *adder) ProcessElement(x int) int { return x + a.n }\nbeam.ParDo(s, &adder{n: 0}, coll)","handlingStrategy":"type-guard","validationCode":"// Go: verify the fn value before passing to beam.ParDo/beam.Combine\nfunc isFnValue(v interface{}) bool {\n    t := reflect.TypeOf(v)\n    if t == nil { return false }\n    switch t.Kind() {\n    case reflect.Func:\n        return true\n    case reflect.Ptr:\n        return t.Elem().Kind() == reflect.Struct\n    default:\n        return false\n    }\n}\nif !isFnValue(fn) { return fmt.Errorf(\"%T is not a func or ptr-to-struct\", fn) }","typeGuard":"func isFnValue(v interface{}) bool {\n    if v == nil { return false }\n    t := reflect.TypeOf(v)\n    if t.Kind() == reflect.Func { return true }\n    return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct\n}","tryCatchPattern":"// Go: fail fast with context when registering DoFns\nif _, err := graph.NewFn(fn); err != nil {\n    return fmt.Errorf(\"registering DoFn %T: %w\", fn, err)\n}","preventionTips":["Pass function values directly, never `&someFunc`.","Struct-based DoFns/CombineFns must be passed as pointers to structs.","Never pass pointers to maps, slices, or scalars as DoFn values."],"tags":["beam-go","reflection","dofn","invalid-argument"],"backgroundTag":"invalid-argument-value","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}