go: proposal: Go 2: deferred code blocks

What version of Go are you using (go version)?

$ go version
go version go1.14.2 linux/amd64

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/damien/.cache/go-build"
GOENV="/home/damien/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/damien/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build816314905=/tmp/go-build -gno-record-gcc-switches"

Proposal: Deferred code blocks

Currently in Go, we can defer function calls like this:

defer f()

However, the problem with this is that we are unable to capture the return values of f and use them, so what someone like myself ends up doing is this:

defer func() { 
   result := f()
   // do something with result
}()

Although it works, it’s not exactly the best way to go about it; we could call f() at the end of the function without using defer then use the results as required.

What I thus propose is that we introduce deferred code blocks which will have this syntax:

defer {
   // do this at the end of the enclosing function
}

What this will do is execute the entire code block at the end of the function in which ‘defer’ is used in. Each line will in the code block will then be run sequentially. To give a more realistic example of where this would be useful:

f, err := os.Create("file.txt")

if err != nil {
   panic(err)
}

defer {
   err := f.Close()

   if err != nil {
      panic(err)
   }
}

As opposed to the current way of going about this:

f, err := os.Create("file.txt")

if err != nil {
   panic(err)
}

defer func() {
   err := f.Close()

   if err != nil {
      panic(err)
   }
}()

In some respect, you could argue that it’s merely cosmetic since all the defer { } is doing is making the otherwise defer func () {}() look neater.

Thoughts?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 36
  • Comments: 17 (12 by maintainers)

Most upvoted comments

So this proposal is simply to avoid func() and ()? Personally, it doesn’t seem worth the high cost that all language syntax changes come with. Also, defer has a nice consistency now, just like go, where it’s always followed by a function call. You would break that consistency, and thus break lots of tools too.

With the different return semantics, this is no longer just a cosmetic proposal.

Having considered the above document before raising this proposal, I still believe this is a worthy addition to Go. As it stands, this proposal is not:

  1. Asking for the current ‘defer’ mechanism to be changed
  2. Preventing people from using defer the usual way

Whilst I appreciate your concern for the integrity of the language spec, it remains an important issue in any language, not just Go, for the spec to always evolve according to the user base whilst maintaining compatibility with previous versions. Although, if you are still sceptical about this proposal, @mvdan, then your opinions are duly noted.

The basic idea here is syntactic sugar around the existing defer statement. But as others have pointed out there are complexities if the new defer block uses a return or a goto out of the block. Also, the emoji voting is not in favor. Therefore, this is a likely decline. Leaving open for three weeks for final comments.

What if you’d able to defer statements?

Given your example:

f, err := os.Create("file.txt")

if err != nil {
   panic(err)
}

defer func() {
   err := f.Close()

   if err != nil {
      panic(err)
   }
}()

It could be:

f, err := os.Create("file.txt")

if err != nil {
    panic(err)
}

defer if err := f.Close(); err != nil {
    panic(err)
}

Yes, you’re adding more ways to write Go code. Which, as I explained, has a high cost - any static analysis tools that currently examine defer statements would need to be updated, for example. This is why the language spec is almost never touched, and even less so for syntax changes.