apache/beam · error

value must be ptr to struct

Error message

value %v must be ptr to struct

What it means

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.

Solutions

  1. Pass the function value itself (e.g., `myFunc`) or a pointer to a struct implementing the DoFn/CombineFn methods.
  2. If using a struct-based DoFn, define ProcessElement (and lifecycle) methods on it and pass `&myDoFn{...}`.
  3. If passing a func, remove the `&` — pointers to funcs are not valid fn values.
  4. Check the value's type with reflect before calling the beam API to fail fast with a clearer message.

Example fix

// before
n := 0
beam.ParDo(s, &n, coll) // ptr to non-struct
// after
type adder struct{ n int }
func (a *adder) ProcessElement(x int) int { return x + a.n }
beam.ParDo(s, &adder{n: 0}, coll)
Defensive patterns

Strategy: type-guard

Validate before calling

// Go: verify the fn value before passing to beam.ParDo/beam.Combine
func isFnValue(v interface{}) bool {
    t := reflect.TypeOf(v)
    if t == nil { return false }
    switch t.Kind() {
    case reflect.Func:
        return true
    case reflect.Ptr:
        return t.Elem().Kind() == reflect.Struct
    default:
        return false
    }
}
if !isFnValue(fn) { return fmt.Errorf("%T is not a func or ptr-to-struct", fn) }

Type guard

func isFnValue(v interface{}) bool {
    if v == nil { return false }
    t := reflect.TypeOf(v)
    if t.Kind() == reflect.Func { return true }
    return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}

Try / catch

// Go: fail fast with context when registering DoFns
if _, err := graph.NewFn(fn); err != nil {
    return fmt.Errorf("registering DoFn %T: %w", fn, err)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/2ebf5cf9e90593e0. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/core/graph/fn.go:102

		f, err := funcx.New(gen.Gen(gen.Name, gen.T, gen.Data))
		if err != nil {
			return nil, err
		}
		return &Fn{Fn: f, DynFn: gen}, nil
	}

	val := reflect.ValueOf(fn)
	switch val.Type().Kind() {
	case reflect.Func:
		f, err := funcx.New(reflectx.MakeFunc(fn))
		if err != nil {
			return nil, err
		}
		return &Fn{Fn: f}, nil

	case reflect.Ptr:
		if val.Elem().Kind() != reflect.Struct {
			return nil, errors.Errorf("value %v must be ptr to struct", fn)
		}

		// Note that a ptr receiver is necessary if struct fields are updated in the
		// user code. Otherwise, updates are simply lost.
		fallthrough

	case reflect.Struct:
		methods := make(map[string]*funcx.Fn)
		annotations := make(map[string][]byte)
		af := reflect.Indirect(val).FieldByName("Annotations")
		if af.IsValid() {
			a, ok := af.Interface().(map[string][]byte)
			if ok {
				annotations = a
			}
		}
		if methodsFuncs, ok := reflectx.WrapMethods(fn); ok {
			for name, mfn := range methodsFuncs {

View on GitHub (pinned to 12126d8942)