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: os: add method (*File).MustClose #34407

Closed
bradfitz opened this issue Sep 19, 2019 · 14 comments
Closed

proposal: os: add method (*File).MustClose #34407

bradfitz opened this issue Sep 19, 2019 · 14 comments

Comments

@bradfitz
Copy link
Contributor

@bradfitz bradfitz commented Sep 19, 2019

Maybe it's time to add:

package os

// MustClose closes f and panics if there's an error.
func (f *File) MustClose() {
     if err := f.Close(); err != nil {
         panic(err)
     }
}

That would make for more readable code compared to all the stuff like:

https://go-review.googlesource.com/c/go/+/196497/1/src/runtime/pprof/pprof.go#b23

(MustClose of course only makes sense on files opened for write)

@gopherbot gopherbot added this to the Proposal milestone Sep 19, 2019
@gopherbot gopherbot added the Proposal label Sep 19, 2019
@mvdan

This comment has been minimized.

Copy link
Member

@mvdan mvdan commented Sep 19, 2019

There is some precedent here:

$ grep Must api/*
api/go1.txt:pkg html/template, func Must(*Template, error) *Template
api/go1.txt:pkg regexp, func MustCompile(string) *Regexp
api/go1.txt:pkg regexp, func MustCompilePOSIX(string) *Regexp
api/go1.txt:pkg syscall (windows-386), func MustLoadDLL(string) *DLL
api/go1.txt:pkg syscall (windows-386), method (*DLL) MustFindProc(string) *Proc
api/go1.txt:pkg syscall (windows-amd64), func MustLoadDLL(string) *DLL
api/go1.txt:pkg syscall (windows-amd64), method (*DLL) MustFindProc(string) *Proc
api/go1.txt:pkg text/template, func Must(*Template, error) *Template

However, ignoring the syscall ones, all the existing Must APIs deal with static input. So if they don't panic when run once in one machine, they technically shouldn't panic anywhere. The same doesn't apply here, since this could depend on the user's filesystem.

Having said that, it doesn't look like the error proposals are making progress at the moment, and I agree that the current state is ugly for examples and simple programs. So I agree that maybe it's time :)

Edit: if added, I think the doc should clarify that Close should be used instead if the calling function returns an error. And perhaps also that it's a shortcut mainly for buggy defer f.Close() lines where the file is being written to, but not anything else.

@bradfitz

This comment has been minimized.

Copy link
Contributor Author

@bradfitz bradfitz commented Sep 19, 2019

I agree that we'd want a larger doc + example.

@jimmyfrasche

This comment has been minimized.

Copy link
Member

@jimmyfrasche jimmyfrasche commented Sep 19, 2019

You could do something like this without adding new api:

func assert(f func() error, msg string) {
  if err := f(); err != nil {
    log.Fatalf(msg + ": %v", err)
  }
}

//...
defer assert(f.Close, "failed to close CPU profile")

With a MustClose method, in the linked use case, you'd have to recover the panic and log.Fatalf anyway so there'd be more boilerplate than with a small helper like this.

@bradfitz

This comment has been minimized.

Copy link
Contributor Author

@bradfitz bradfitz commented Sep 19, 2019

With a MustClose method, in the linked use case, you'd have to recover the panic and log.Fatalf anyway so there'd be more boilerplate than with a small helper like this.

Why would I "have to" do that? I could just, like, let it panic.

@jimmyfrasche

This comment has been minimized.

Copy link
Member

@jimmyfrasche jimmyfrasche commented Sep 19, 2019

I meant that you "have to" do that it in order to get the same behavior as in the code as authored currently. Just letting it panic is fine, too.

@cherrymui

This comment has been minimized.

Copy link
Contributor

@cherrymui cherrymui commented Sep 19, 2019

My question for not using defer f.Close() is, what should I do if Close fails? In the case of writing a profile, I'm not sure crashing the program is what I really want. Perhaps just move on, maybe print a message. And in many cases it is at the end of main, so it doesn't really matter.

@bradfitz

This comment has been minimized.

Copy link
Contributor Author

@bradfitz bradfitz commented Sep 19, 2019

@cherrymui, I'm not proposing we take away the Close method. If that's what you want, use it.

@cespare

This comment has been minimized.

Copy link
Contributor

@cespare cespare commented Sep 19, 2019

Or ioutil.MustClose(io.Closer)?

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Sep 19, 2019

I don't think we want to encourage panic as a good approach to a dynamic error. As @mvdan says this idea is quite different from regexp.MustCompile.

An alternative would be

func (f *File) CheckClose(f func(error)) {
    if err := f.Close(); err != nil {
        f(err)
    }
}

To be used as

    defer f.CheckClose(func(err error) { log.Fatalf("failed to close CPU profile: %v", err) })

I'm not sure I like that either but I like it better that MustClose.

@bradfitz

This comment has been minimized.

Copy link
Contributor Author

@bradfitz bradfitz commented Sep 19, 2019

Or ioutil.MustClose(io.Closer)?

SGTM:

https://godoc.org/go4.org/must#Close

    defer must.Close(f)
@beoran

This comment has been minimized.

Copy link

@beoran beoran commented Sep 20, 2019

Seeing that this can be done in an external package, like go4 above, I think that there's no need to add this to the standard Go library.

@mvdan

This comment has been minimized.

Copy link
Member

@mvdan mvdan commented Sep 20, 2019

@beoran note that the origin of this proposal is that multiple code samples in the standard library ignore Close errors when they shouldn't. They can't depend on third-party modules, and adding the manual defer func() { adds four lines of boilerplate, as can be seen in https://go-review.googlesource.com/c/go/+/196497.

This is not to say that we absolutely need something in std, but a third-party function doesn't really help the original problem.

defer f.CheckClose(func(err error) { log.Fatalf("failed to close CPU profile: %v", err) })

Combining this with @cespare's idea to make this apply to any closer:

defer ioutil.MustClose(f, func(err error) { log.Fatalf("failed to close CPU profile: %v", err) })

It would be much nicer if we could provide a std function directly, when no extra context is needed:

defer ioutil.MustClose(f, log.Fatal)
defer ioutil.MustClose(f, panic)

But I don't see any way to accomplish that without interface{} and an icky godoc.

@networkimprov

This comment has been minimized.

Copy link

@networkimprov networkimprov commented Sep 20, 2019

defer ioutil.CloseOrPanic(f)
defer ioutil.CloseOrPrintf(f, log.Fatalf) // func(fmt string, a ...interface{})
defer ioutil.CloseOrPrintf(f, fmt.Printf)

CloseOrPrintf probably isn't the right name... Fmtf? Logf?

@bradfitz

This comment has been minimized.

Copy link
Contributor Author

@bradfitz bradfitz commented Sep 25, 2019

I retract. Not worth keeping it open.

Maybe one day something will happen with Go error handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
9 participants
You can’t perform that action at this time.