Repository navigation
proposal: text/template, html/template: add MethodResolver and ExecuteWithResolver #82062
Description
Proposal Details
Executing a template makes reflect.Value.MethodByName reachable: evalField uses it to look up names such as .Name. The linker then keeps exported methods that would otherwise be unused (#72895, #62024). In the Kubernetes prototype, disabling method lookup reduces kubectl's binary size by 29% and kubeadm's by 48%. gRPC servers can also reach this lookup through x/net/trace.
I propose a second entry point that lets the caller provide method lookup:
package template // text/template; html/template mirrors it with a MethodResolver alias
// A MethodResolver resolves the name in a reference such as .Name to a
// function to call.
type MethodResolver interface {
// Resolve returns the function to call for name on receiver, or nil to
// fall through to struct fields and map keys. The function is called
// with the reference's arguments; a method value such as
// receiver.Name is the usual result. The receiver is the value being
// evaluated, or a pointer to it when it is addressable.
Resolve(receiver any, name string) any
}
// ExecuteWithResolver is like Execute but resolves methods with resolver.
// A nil resolver disables method lookup.
func (t *Template) ExecuteWithResolver(wr io.Writer, data any, resolver MethodResolver) errorSemantics:
Executeis unchanged and keeps resolving methods by reflection.- A nil resolver resolves names to struct fields and map keys only. Missing fields and map keys follow the same error and
missingkeyrules asExecute. This is the mode requested forExecuteLitein proposal: text/template,html/template: Template.ExecuteLite skip resolution to methods to allow DCE #72895. - A non-nil resolver can select methods by receiver type or return other functions. The returned function receives the template's arguments. Returning a non-function value other than nil or panicking in
Resolvecauses an execution error. - Nested
{{template}}executions use the same resolver. - Template functions and the
StringandErrormethods used when printing values still run; this is not a sandbox. - When every template execution in a program uses a nil or non-reflective resolver, template execution does not make dynamic method lookup reachable. The linker can then drop methods that nothing else references.
I also prototyped a build tag, a nil-only execution method, and a FuncMap of receiver-first functions. A build tag applies to the whole binary and leaves the choice to whoever builds it. A nil-only method disables lookup but cannot provide custom method resolution. A FuncMap maps names to functions; dispatch by receiver type belongs inside each function.
MethodResolver receives both the receiver and the name. It needs no reflect types in the API, and callers that only need fields and map keys can pass nil.
Prototype: master...dims:go:template-method-resolver, with tests, five cmd/link dead-code fixtures, api/next and release notes; the package tests, the linker fixtures and the API check pass. With every Kubernetes call site passing nil, kubectl shrinks from 55,730,336 to 39,329,952 bytes and kubeadm from 69,464,224 to 35,799,200 bytes. There are zero reflect-method edges; help output and 91 template cases are byte-identical to the unmodified Kubernetes build.
Related compiler fix: #82047 and CL 846145. A method expression for reflect.Value.MethodByName was not marked when used as a value, and the linker pruned the methods it looked up.
Open question: the name. The prototype uses ExecuteWithResolver and MethodResolver.
Activity
text/template, html/template: add MethodResolver and ExecuteWithResolver
gopherbot commentedon Oct 7, 2026
Change https://go.dev/cl/846625 mentions this issue:
text/template, html/template: add MethodResolver and ExecuteWithResolvergabyhelp commentedon Oct 7, 2026
Related Issues
Related Code Changes
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
AlexanderYastrebov commentedon Oct 7, 2026
What do you think about making it generic like: