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

io/ioutil: add WriteNopCloser #22823

Open
robpike opened this Issue Nov 20, 2017 · 3 comments

Comments

Projects
None yet
5 participants
@robpike
Copy link
Contributor

robpike commented Nov 20, 2017

The type ioutil.NopCloser allows us to add a no-op Close to an existing Reader when a ReadCloser is required.

For symmetry, there should also be a ioutil.WriteNopCloser to do the same for Write.

The name can be argued about. Get out your bikeshed paint brushes.

@robpike robpike changed the title ioutil: add NopWriterCloser ioutil: add ioutil.WriteNopCloser Nov 20, 2017

@odeke-em odeke-em changed the title ioutil: add ioutil.WriteNopCloser io/ioutil: add WriteNopCloser Nov 20, 2017

@karalabe

This comment has been minimized.

Copy link
Contributor

karalabe commented Nov 20, 2017

🔴 :trollface:

@smasher164

This comment has been minimized.

Copy link
Contributor

smasher164 commented Nov 21, 2017

The permutations go from viable to esoteric 😏:

WriteNopCloser
NopWriteCloser
NopCloseWriter
WriteCloseNopper
CloseWriteNopper
CloseNopWriter

@titanous titanous added this to the Proposal milestone Nov 21, 2017

@titanous titanous added the Proposal label Nov 21, 2017

@tylerchr

This comment has been minimized.

Copy link

tylerchr commented Sep 18, 2018

I support this proposal out of practical experience. I just ran across a use case where I needed just this function, and settled for a suboptimal design because it didn't exist.

My application produces a log file whose path is configurable via environment variable. If the variable is provided, it's assumed to be a file path and an *os.File (i.e., an io.WriteCloser) is opened; if the variable is not provided I want to use ioutil.Discard. I wanted to abstract this logic into a function that returns an io.WriteCloser so I could defer x.Close() in the caller:

func main() {
    logWriter := GetLogWriter()
    defer logWriter.Close()
}

func GetLogWriter() io.WriteCloser {
    if path, ok := os.LookupEnv("LOG_PATH"); ok {
        if file, err := os.Create(path); err == nil {
            return file
        }
    }
    return ioutil.WriteNopCloser(ioutil.Discard)
}

But, having no trivial way to get an io.WriteCloser from an ioutil.Discard, GetLogWriter wasn't possible to write[^1] and I settled for some rather convoluted logic directly in the caller:

func main() {

	var logWriter io.Writer = ioutil.Discard

	if path, ok := os.LookupEnv("LOG_PATH"); ok {
		if file, err := os.Create(path); err != nil {
			panic("sad day")
		} else {
			defer file.Close()
			logWriter = file
		}
	}

}

This works, but it's not great. Specifically, the conditional defer looks like a guaranteed future bug because it breaks from the familiar idiomatic usage.

[^1]: Yes, I could have implemented a variant of ioutil.WriteNopCloser on my own, but the point is that I didn't. I'm settling for more dangerous code instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment