Is it a good idea to handle errors like that?

Hello everyone.

I want to tell you about a problem I’ve encountered, a solution I’ve found and ask you whether my solution is elegant or dirty in terms of Go.

Problem

Let’s start with a fact that I had to work with bytes. I had to write bytes in many little parts. I used io.Writer for this. As you may know, it has Write method that returns a number of written bytes and error.

That is the main problem. I had to check error after every single writing action. Moreover, I don’t have any means to handle the error; if it’s happened, then it’s over. And having to handle every error (I had many Write calls!) was unbearably tedious and cumbersome.

Solution

A day after I’ve came up with a solution. I’ve made an unexported io.Writer-compliant struct panicWriter:

import "io"

type panicWriter struct {
    realWriter io.Writer
}

func (pw panicWriter) Write(data []byte) (n int, err error) {
    n, err = pw.realWriter.Write(data)
    if err != nil {
        panic(err)
    }

    return
}

Now, I just defer a recovering function in uppermost writing function (I’m currently making a library) which sets up error returning value, so now I can use every advantage of io.Writer without checking returned error. So techically it’s an exception handling simulation within a library, so panic doesn’t leave it.


So, is it a good decision? May there be any caveats? I started studying Go 2 weeks ago, and I might still misunderstand its philosophy.

It might be a stupid question, but I strongly believe that new programmers should discover dos and don’ts of new language as soon as possible.

1 Like

Hi

I think it is a no no to use panic as an exception like mechanism. It should be reserved for unrecoverable errors

https://gobyexample.com/panic

A panic typically means something went unexpectedly wrong. Mostly we use it to fail fast on errors that shouldn’t occur during normal operation, or that we aren’t prepared to handle gracefully.

You could do it like it’s suggested in this video https://www.youtube.com/watch?v=1B71SL6Y0kA something like this:

package main

import (
	"fmt"
	"io"
	"os"
)

type panicWriter struct {
	realWriter io.Writer
	err        error
}

func (pw panicWriter) Write(data []byte) (n int) {
	if pw.err != nil {
		return
	}
	n, pw.err = pw.realWriter.Write(data)
	return
}

func main() {
	p := panicWriter{realWriter: os.Stdout}
	p.Write([]byte("Hello"))
	p.Write([]byte(" world"))
	p.Write(]byte("!"))

	if p.err != nil {
		// Handle it
		fmt.Println("Error:", p.err)
	}
}


1 Like

This is a good idea, it should work for me. Thank you!

1 Like

Good! Maybe you could rename it to SafeWriter to follow the convention in the video (it doesn’t panic anymore so panicWriter is a bit misleading :wink:

I think it is a no no to use panic as an exception like mechanism. It should be reserved for unrecoverable errors

@johandalabacka I disagree. A core aspect of panics, and a key factor in their usefulness, is the fact that they destroy the stack until recovered much like exceptions. The section on Recovery in Effective Go discusses this. It is essential that the library recover the panic and never expose it to the caller, but as long as that’s the case it’s a nice way to escape complex function call stacks without returning an error back up every function call.

Imagine a parser, which is often very indirectly recursive. That is to say, call stacks such as A -> B -> C -> D -> B -> C -> A -> B -> C are very common. In a case like this, panic is the perfect way to go all the way back to the top most function call when an error occurs. This is similar to the example given in Effective Go.

Yes @kylewood Your right. Of course there are cases then it is impractical to return errors and panicing makes the logic much clearer. On the page you reference it also says:

Useful though this pattern is, it should be used only within a package. Parse turns its internal panic calls into error values; it does not expose panics to its client. That is a good rule to follow.

I think it is easy for beginners of go to look for some kind of exception handling if they come from python, Java or some other language with exceptions and find and overuse panics.

Yes, this is helpful when you want error to propagate by itself. But in my case, panic was inside of method of Writer interface, object of which I can send to some other library, and this is where it becomes dangerous.

I wonder, what would happen if I were to use some library (let’s call it X) that wraps this writer? What if library X uses panic-recover pattern inside itself for easier error handling?

I think it would result in false alarm inside library X, may panicWriter fail inside it. I don’t think I can predict what would happen then.