apache/beam · error
substituting type : type not bound
Error message
substituting type %v: type not bound
What it means
typex.Substitute replaces every universal (variable) type in a FullType using the binding map produced by Bind. If a type variable's name is absent from the map, substitution cannot proceed and this error is returned.
Solutions
- Build the map via typex.Bind over the same models used to define the type variables, rather than hand-writing it.
- Check the variable name in the message against your map keys; fix the spelling or add the missing binding.
- If the type should have no free variables, verify it was constructed with concrete types, not typex.T/X placeholders.
- Call SubstituteFull on a small test FullType with the same map to isolate which variable is unbound.
Example fix
// before
m := map[string]reflect.Type{"x": reflect.TypeOf("")}
typex.Substitute(t, m) // variable is named "T"
// after
m := map[string]reflect.Type{"T": reflect.TypeOf("")}
typex.Substitute(t, m) Defensive patterns
Strategy: validation
Validate before calling
func allBound(t typex.FullType, m map[string]reflect.Type) bool {
switch t.Class() {
case typex.Universal:
_, ok := m[t.Type().Name()]
return ok
default:
for _, c := range t.Components() {
if !allBound(c, m) {
return false
}
}
return true
}
} Type guard
func isFullyConcrete(t typex.FullType) bool {
if t.Class() == typex.Universal {
return false
}
for _, c := range t.Components() {
if !isFullyConcrete(c) {
return false
}
}
return true
} Try / catch
out, err := typex.Substitute(t, m)
if err != nil {
return fmt.Errorf("substitution: %w (check map keys against variable names)", err)
} Prevention
- Derive the substitution map from typex.Bind instead of hand-writing map literals.
- Copy variable names from the same typex.T/typex.X constants used to build the model, never retype them.
- Before substituting, assert the FullType's free variables all exist in the map.
- Reuse one shared Bind result across all types derived from the same model set.
When it happens
Trigger: Calling typex.Substitute(t, m) where t contains a Universal type whose Name() has no key in m — usually because Bind was never run on a type containing that variable, or the map came from a Bind over a different model set.
Common situations: Users hand-construct substitution maps (map[string]reflect.Type) with a typo in the variable name (e.g. "x" vs "T"); or reuse a Bind result across FullTypes that reference additional variables; seen when building custom coders or graph rewriting in Beam Go.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- unexpected aggregate
- AfterProcessingTime trigger set without a delay or…
- array len mismatch. decoding
- At least one subtrigger required for composite triggers.
- attempted to add namespace to missing coder id
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/049efaf83454d2ed.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/go/pkg/beam/core/typex/fulltype.go:416
func Substitute(list []FullType, m map[string]reflect.Type) ([]FullType, error) {
var ret []FullType
for _, t := range list {
repl, err := substitute(t, m)
if err != nil {
return nil, err
}
ret = append(ret, repl)
}
return ret, nil
}
func substitute(t FullType, m map[string]reflect.Type) (FullType, error) {
switch t.Class() {
case Universal:
name := t.Type().Name()
repl, ok := m[name]
if !ok {
return nil, errors.Errorf("substituting type %v: type not bound", name)
}
return New(repl), nil
case Container:
comp, err := substituteList(t.Components(), m)
if err != nil {
return nil, err
}
if IsList(t.Type()) {
return New(reflect.SliceOf(comp[0].Type()), comp...), nil
}
return nil, errors.Errorf("unexpected aggregate %v, only slices allowed", t)
case Composite:
comp, err := substituteList(t.Components(), m)
if err != nil {
return nil, err
}
return New(t.Type(), comp...), nil
View on GitHub (pinned to 12126d8942)