What does reflect.MakeFunc create, and when would you reach for it in Go?
answer
- the opposite direction from Call
- dynamic body behind a static signature
- you supply the func type yourself
- callback speaks []reflect.Value both ways
- callers cannot tell it is reflective
basics
~20 sreflect.MakeFunc builds a function value of a func type you choose at runtime, implemented by a callback that receives arguments as []reflect.Value and returns results the same way. Ordinary typed code can then call it like any other function.
solid answer
~40 s`reflect.MakeFunc(typ, fn)` takes a `reflect.Type` of func kind and a callback `func(args []reflect.Value) []reflect.Value`, and returns a `reflect.Value` holding a real function of that exact signature. Invoking it — through `Call`, or after `Interface()` and a type assertion, or once assigned into a func-typed field — runs your callback with the arguments boxed as `reflect.Value`s, and the values you return become the declared results. It is the mirror image of `MethodByName` plus `Call`: `Call` invokes an existing method from dynamic data, while `MakeFunc` gives dynamic code a *static* signature that ordinary callers can use without knowing reflection is involved. Typical uses are same-signature wrappers — auditing, timing, retrying — and filling in func-typed fields from configuration. The callback must return exactly the declared number of results, each assignable to its declared type.
code
go · 10 lines// wrap returns a function with the same signature as fn that logs each call.
// fn must not be variadic.
func wrap(name string, fn any) any {
v := reflect.ValueOf(fn)
body := func(in []reflect.Value) []reflect.Value {
log.Printf("command %s invoked with %d arguments", name, len(in))
return v.Call(in)
}
return reflect.MakeFunc(v.Type(), body).Interface()
}go deeper
Know that reflect.MakeFunc exists and that it produces a callable function whose body is written in terms of []reflect.Value rather than concrete parameters.
Explain the two arguments, where the func type usually comes from, and the contract the callback owes on its results. Contrast it with dispatching an existing method by name.
Judge when a reflective trampoline is worth its opacity: what it costs in allocation per call, in unreadable stack traces, and in static analysis that can no longer follow the edge.
Decide whether a codebase should contain generated or reflective wrappers at all, given that type parameters now cover most of what MakeFunc was historically used for.
## Two directions of reflection `MethodByName` plus `Call` goes from data to a call: you hold a name and a slice of arguments, and you invoke something that already exists. `reflect.MakeFunc` goes the other way. You hold an implementation that only knows how to work with `reflect.Value`s, and you need it to *look* like a normal Go function so that ordinary, statically typed code can call it. That is the gap `MakeFunc` fills. ## The signature func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value `typ` must be of func kind — anything else panics. The returned `reflect.Value` holds a genuine function of that type. When something calls it, the runtime boxes the incoming arguments into a `[]reflect.Value` in declaration order, hands them to `fn`, and unboxes what `fn` returns into the declared results. Where does `typ` come from? Usually from a function you already have: `reflect.ValueOf(existing).Type()`. That is exactly what a wrapper needs — the same signature, a different body. ## The contract on the callback The callback must return a slice of length `typ.NumOut()`, and the i-th element must be assignable to `typ.Out(i)`. Getting this wrong panics at call time, not at construction time, which makes it a nasty defect to find. The subtle case is a nil result: you cannot return an invalid `reflect.Value` to mean "no error". You return a valid `reflect.Value` of the declared result type holding its zero value — `reflect.Zero(typ.Out(1))` for a nil `error`. One more caveat: if `typ` is variadic, the callback receives the variadic arguments already collected into a slice as the final element, so a wrapper that forwards them must use `CallSlice` rather than `Call`. Wrappers that do not need variadic support usually just refuse it. ## Using the result from typed code Three ways out: - `v.Interface().(func(string) error)` — assert it back to the concrete func type and call it normally. - Assign it into a func-typed variable or struct field through reflection, so existing code picks it up with no idea anything is dynamic. - Call it with `v.Call(...)`, which is the least interesting option, because if you were staying inside reflection you did not need `MakeFunc` at all. ## When it earns its place **Same-signature wrappers.** An admin command bus wants every dispatched command audited. Rather than writing one wrapper per handler signature, you build one generic wrapper: take the original function's type, `MakeFunc` a replacement of that type whose callback logs and then forwards, and hand the caller something indistinguishable from the original. **Filling func fields.** A struct with func-typed hooks can be populated from a registry keyed by name, where the implementations are chosen at run time. **Adapting shapes** that Go's own type system cannot express as one function — historically the main motive, and much less pressing now that type parameters exist. If a generic function or a small interface can express what you need, they are faster, checked by the compiler and readable by every tool in the ecosystem. Reach for `MakeFunc` when the signature genuinely is not known until run time. ## What it costs Every call through a `MakeFunc` value boxes its arguments and unboxes its results, so it is materially slower than a direct call and it allocates. It is also invisible to a reader: the caller sees a normal func value, and nothing at the call site hints that a reflective trampoline sits underneath. That is precisely the property that makes it useful for transparent wrapping and dangerous as a default — stack traces through it are harder to read, and static analysis cannot follow the edge from the wrapper to the wrapped implementation.
- How is MakeFunc different from resolving a method with MethodByName and calling it?`MethodByName` plus `Call` invokes something that already exists, and the caller must be holding `reflect.Value`s. `MakeFunc` produces a value whose *type* is an ordinary func type, so statically typed code calls it with no reflection at the call site — the dynamism is hidden behind a static signature rather than exposed at the call.
- What exactly must the callback return, and what is the trap with a nil error result?Exactly `typ.NumOut()` values, each assignable to `typ.Out(i)`; a wrong count or type panics at call time, not at construction. For a nil `error` you must return a valid `reflect.Value` holding the zero value of the error type — `reflect.Zero(typ.Out(i))` — because an invalid Value is not a legal result.
- You have a reflect.Value from MakeFunc. How does ordinary typed code invoke it?Call `Interface()` and type-assert to the concrete func type, for example `v.Interface().(func(string) error)`, then call it normally. Alternatively assign it into a func-typed variable or struct field so existing code picks it up unchanged. Using `v.Call(...)` works but defeats the point of building a statically typed func.
- When would you not use MakeFunc?Whenever the shape is known at compile time. A type parameter, a small interface, or a plain closure gives you the same adaptation with compile-time checking, no boxing per call and stack traces a reader can follow. `MakeFunc` earns its cost only when the signature genuinely is not known until run time.
saying these in an interview costs you the question
- Thinks MakeFunc returns a func you can call without a type assertion
- Passes a non-func reflect.Type as the first argument
- Returns an invalid reflect.Value to mean a nil error
- Confuses MakeFunc with invoking an existing method by name
- Forwards variadic arguments with Call instead of CallSlice