Description
Background: In Go, named functions must be declared at package level. The scope of a func
declaration includes the function body, so named functions may be directly or mutually recursive.
func f() { ... f() ... }
Local functions, which may have free variables, must be anonymous. Therefore they cannot call themselves recursively. Instead, we must resort to the var trick, in which a local variable is first declared, then assigned the value of an anonymous function whose body refers to the variable:
func f() {
var g func()
g = func() { ... g() ... }
... g() ...
}
This limitation has three consequences:
(1) The source code for local functions is unnecessarily verbose.
(2) The generated code for local functions is unnecessarily inefficient. Because all calls to g are superficially dynamic, the compiler generates dynamic CALL instructions and inlining is defeated. (To be clear, this is problem is entirely fixable in the compiler.)
(3) Local functions cannot be generic, because there is no way to declare type parameters. In some cases this forces a number of declarations that could otherwise all be local to have to be spilled to the top level, polluting the package namespace.
Proposal: We propose to permit func
declarations as statements, so the above code could be written:
func f() {
func g() { ... g() ... }
... g() ...
}
This would allow local functions to be generic:
func f() {
func g[T any]() { ... g() ... }
... g() ...
}
This does create a minor parsing ambiguity between a DeclStmt of a FuncDecl, and an ExprStmt whose leftmost subtree is a FuncLit, for example:
func f() {
func() { ... } () // immediately applied lambda
func g() { ... } // local func declaration
}
This ambiguity is easily resolved with an extra token of lookahead: when parsing a statement, if func
is followed by an identifier, it's a DeclStmt, otherwise, it's an ExprStmt.
Initially at least, we should require that references to local generic functions need explicit instantiation, to avoid the issue that appears in ML where type inference is exponential in the depth of nested lets. Perhaps that can be relaxed.
I expect the most difficult part of the implementation to be the elimination of assumptions that FuncDecls are not syntactically nested, throughout the compiler and x/tools. It is of course a breaking change to clients of go/ast.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status