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: explicitly scoped defers #54249
Comments
This can be achieved without control flow modification already. file, err := os.Open(path)
if err != nil {
return err
}
var closeOnce sync.Once
defer closeOnce.Do(func() { file.Close() })
// use file here
closeOnce.Do(func() { file.Close() })
// do other stuff. |
@tkw1536 certainly that's another approach that currently works. The aim of this proposal is to make doing the right thing so simple, straightforward and lightweight that everybody does it by default, and we can even add lints which use rules of thumb to push people in the right direction. The fact that the pattern you suggest isn't used in a very high percentage of defers today is evidence we could do with something better here. |
It should be noted that today you can also use anonymous functions for this. (Admittedly it can be a little awkward with return values, but at least you don't have to define a totally separate helper function.) func foo(path) error {
err := func() error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// use file here
}()
if err != nil {
return err
}
// more stuff here
} That said, I can definitely sympathize with the desire for a more convenient syntax. However, one thing to note about this proposal is it will lead to a lot of nesting in case there are several defers. f1, err := os.Open(...)
if err != nil { ... }
defer f1.Close() {
f2, err := os.Open(...)
if err != nil { ... }
defer f2.Close() {
...
}
} Also this specific proposal introduces a potential ambiguity in the grammar. The following could either be a normal defer statement followed by a block (as is the case today), or a scoped defer statement (your proposal). You could argue that it is clearly the former (to align with how defer f1.Close()
{
...
} |
Not a super fan of requiring a dedicated block to scope the defer (to see why imagine you need to have N files open: it's going to quickly start drifting right).
Also, it would force all variables in the defer scope to become inaccessible outside the scope, and this would manifest in a weird asymmetry: e.g. in the example above in Not advocating for a specific alternative, but a shorthand syntax that just allowed users to trigger the execution of a defer immediately (and not executing it when the function returns) would be more desirable (that is basically what the |
@rittneje @CAFxX thanks for your points. All sensible! Firstly, I think they don't necessarily kill the proposal. I don't believe the ambiguity point is correct. This is currently illegal:
And similarly it would be required for the block to begin on the same line as the defer statement. It might be worth having a linter check for a defer followed by a scope on the next line if it ends up being a common pitfall, but I doubt that. I think the majority of cases don't have nesting defers, so I'm ok having heavily indented files when they do occur (or using an alternative option in such cases). Similarly, based on my experience with C#'s I agree it is unfortunate that the file would still be in scope after the block exits, but that is something a linter should be able to detect in most situations. That said I have some alternatives, which if people are interested in I'm happy to open proposals for. alternative 1 - add
|
To fix the nesting, how about the ability to specify a label for a defer, then use the label to specify a scope? In other words, something like func Example() err {
files:
{
file1, err := os.Open(path1)
if err != nil {
return err
}
defer files, file1.Close()
file2, err := os.Open(path2)
if err != nil {
return err
}
defer files, file2.Close()
// Do stuff with files.
} // Deferred stuff runs here or when control flow otherwise leaves the labeled block.
// The defers above have both run by this point.
} |
I don't know if I like this idea in general, but I do like @DeedleFake 's suggestion quite a bit more than the original proposal on this issue. I'm not 100% sure but I don't think we need the comma. |
I didn't have the comma originally, but it felt inconsistent with a multi-argument |
I'm fine with the label approach, but I would really prefer if there was a way to refer to the current scope without a label (as i would expect the current scope to be the most commonly used, as is the case for break and continue). Just to clarify: I'm obviously not suggesting to change the current defer behavior |
As discussed above, there are already ways to scope a Therefore, this is a likely decline. Leaving open for four weeks for final comments. |
No further comments. |
Author background
Related proposals
Proposal
Currently defer statements run at the end of a function. This means that resources can live far longer than they should, especially if defer is called inside a loop, when counter-intuitively the defer statements won't be called till the loop exits. This can even cause a memory leak if it's an infinite loop.
This can also be especially problematic if the resource being closed is a lock, which should be taken for the shortest time possible.
The solution to this is either not to use defer in such cases (which means the resource wont be closed in the case of a panic), or to move out the usage of the resource into it's own function.
The latter is certainly a valid solution, but in practice developers are lazy. Given the effort of extracting a function in order to narrow the scope of a defer statement, they are reasonably likely to just leave it as it is, and let the resource live a little longer than it should, especially if extracting the function means modifying complex control flow.
This language change provides a lightweight, easy to use and intuitive syntax to allow scoping a defer to a particular block. This makes it trivial to ensure a resource only lives as long as it needs to.
We allow defer to be scoped to a particular lexical block, indicated by starting the block on the same line as the defer statement (similiarly to if). The defer statement will run as soon as the block is exited.
syntax
We allow an optional block after a defer statement
semantics
The semantics of the block itself are exactly the same as a normal block.
Costs
The text was updated successfully, but these errors were encountered: