Skip to content

proposal: spec: local func declarations #73502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
adonovan opened this issue Apr 25, 2025 · 7 comments
Open

proposal: spec: local func declarations #73502

adonovan opened this issue Apr 25, 2025 · 7 comments
Labels
LanguageChange Suggested changes to the Go language LanguageProposal Issues describing a requested change to the Go language specification. Proposal
Milestone

Comments

@adonovan
Copy link
Member

adonovan commented Apr 25, 2025

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.

@gopherbot gopherbot added this to the Proposal milestone Apr 25, 2025
@adonovan adonovan added the LanguageChange Suggested changes to the Go language label Apr 25, 2025
@fzipp
Copy link
Contributor

fzipp commented Apr 25, 2025

Would this have the same scoping and shadowing rules as the other three declaration keywords (const, var, type)?

func f() {
    func g(s string) {
        // different g in g
        func g(x int) { ... }
    }

    { // new block, new g
        func g() { ... }
    }
}

@fzipp
Copy link
Contributor

fzipp commented Apr 25, 2025

Would this allow methods for local types?

func f() {
    type T struct{}
    func (t T) method() { ... }
}

@randall77
Copy link
Contributor

Can a declaration have no body, and be implemented in assembly? If so, what is its name?

Also #71562, methods on function-local types?

@fzipp
Copy link
Contributor

fzipp commented Apr 25, 2025

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.

Re. my previous question: This probably prevents methods for local types.

@gabyhelp gabyhelp added the LanguageProposal Issues describing a requested change to the Go language specification. label Apr 25, 2025
@fzipp
Copy link
Contributor

fzipp commented Apr 25, 2025

This would capture outer variables like a closure, right?

for n := range 10 {
	func square() int { return n * n }
	fmt.Println(square())
}

@fzipp
Copy link
Contributor

fzipp commented Apr 25, 2025

Are these function definitions fixed like package-level functions and cannot be re-assigned in the same scope?

func f() {
	func g() int { return n * n }
	g = func() int { return n + n } // not possible?
}

@adonovan
Copy link
Member Author

In answer to all the questions:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LanguageChange Suggested changes to the Go language LanguageProposal Issues describing a requested change to the Go language specification. Proposal
Projects
Status: No status
Development

No branches or pull requests

5 participants