Skip to content

proposal: spec: local func declarations #73502

Open
@adonovan

Description

@adonovan

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

No one assigned

    Labels

    LanguageChangeSuggested changes to the Go languageLanguageChangeReviewDiscussed by language change review committeeLanguageProposalIssues describing a requested change to the Go language specification.Proposal

    Type

    No type

    Projects

    Status

    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions