Skip to content
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

proposal: Go 2: return func() from defer for early execution #24349

Closed
dave opened this issue Mar 11, 2018 · 7 comments
Closed

proposal: Go 2: return func() from defer for early execution #24349

dave opened this issue Mar 11, 2018 · 7 comments
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@dave
Copy link
Contributor

dave commented Mar 11, 2018

I often find I'd like to use defer in the middle of a larger function. My options are either to split the functionality out into a smaller function, or duplicate the code I'd like to defer:

// ...
file, err := os.Create(foo)
if err != nil {
	return err
}
if _, err := file.Write(bar); err != nil {
	file.Close()
	return err
}
if _, err := file.Write(baz); err != nil {
	file.Close()
	return err
}
file.Close()
// ...

I propose that defer should return a func(). When called, it will execute the deferred code, and prevent it from being executed when the function returns.

This will make the code clearer, and would remain backwards compatible:

// ...
file, err := os.Create(foo)
if err != nil {
	return err
}
close := defer file.Close()
if _, err := file.Write(bar); err != nil {
	return err
}
if _, err := file.Write(baz); err != nil {
	return err
}
close()
// ...
@gopherbot gopherbot added this to the Proposal milestone Mar 11, 2018
@dave dave changed the title proposal: return func() from defer proposal: return func() from defer for early execution Mar 11, 2018
@faiface
Copy link

faiface commented Mar 11, 2018

You can defer in the middle of a larger function like this:

// ...
err := func() error {
	file, err := os.Create(foo)
	if err != nil {
		return err
	}
	defer file.Close()
	if _, err := file.Write(bar); err != nil {
		return err
	}
	return nil
}()
// ...

@mvdan mvdan added LanguageChange v2 A language change or incompatible library change labels Mar 11, 2018
@mvdan
Copy link
Member

mvdan commented Mar 11, 2018

This would effectively mean that defers should be expressions instead of statements, which is a pretty substantial change to the language. You'd also lose consistency with go statements, unless you'd make those expressions too.

Also note that defers stack, yet your proposal is for one returned func per defer. If you wanted to do multiple defers with the returned funcs, you'd have to add the boilerplate for keeping all of the funcs and calling them.

@dave
Copy link
Contributor Author

dave commented Mar 11, 2018

@faiface yes and I've used that pattern a couple of times, but it doesn't feel great to be bundling things up in a closure just so I can use defer...

@creker
Copy link

creker commented Mar 11, 2018

I often stumble upon the same problem and either split my function into a couple of smaller ones or extract a couple of lines into closure. But I'm not so sure this warrants a change to defer.

One could argue that the mere fact that you need that kind of change indicates a problem with the code. And, instead of making elaborate tricks with defer, it would be much clearer to extract a helper routine.

Either way, your solution raises a lot of design and implementation questions some of which @mvdan already pointed out.

@mvdan
Copy link
Member

mvdan commented Mar 11, 2018

To add to @faiface's point of splitting into two funcs or adding an anonymous function; I believe this all comes down to avoiding large blocks with lots of unnecessary variables in the scope. Even if you could "activate" that defer early in the original example, the rest of the block would still have the file in scope, which is never a good thing.

@ianlancetaylor ianlancetaylor changed the title proposal: return func() from defer for early execution proposal: Go 2: return func() from defer for early execution Mar 12, 2018
@ianlancetaylor
Copy link
Contributor

If we write this package:

package defer

func Once(f func()) func() {
	done := false
	return func() {
		if !done {
			f()
			done = true
		}
	}
}

then you can write your code as

// ...
file, err := os.Create("/tmp/xy")
if err != nil {
	return err
}
close := defer.Once(func() { file.Close() })
defer close()
if _, err := file.Write([]byte("a")); err != nil {
	return err
}
if _, err := file.Write([]byte("b")); err != nil {
	return err
}
close()
// ...

which requires one extra line, and requires an explicit func, but all-in-all does not seem so bad.

@dave
Copy link
Contributor Author

dave commented Mar 14, 2018

That looks like a reasonable suggestion. I'll close this issue. Thanks @ianlancetaylor !

@dave dave closed this as completed Mar 14, 2018
@golang golang locked and limited conversation to collaborators Mar 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

6 participants