The Perils of Pointers in the Land of the Zero-Sized Type

Hey everyone,

Imagine writing a translation function that transforms internal errors into public API errors. In the first iteration,
you return nil when no translation takes place. You make a simple change — returning the original error instead of
nil — and suddenly your program behaves differently:

translate1: unsupported operation
translate2: internal not implemented

These nearly identical functions produce different results (Go Playground). What’s your guess?

type NotImplementedError struct{}

func (*NotImplementedError) Error() string {
	return "internal not implemented"
}

func Translate1(err error) error {
	if (err == &NotImplementedError{}) {
		return errors.ErrUnsupported
	}

	return nil
}

func Translate2(err error) error {
	if (err == &NotImplementedError{}) {
		return nil
	}

	return err
}

func main() {
	fmt.Printf("translate1: %v\n", Translate1(DoWork()))
	fmt.Printf("translate2: %v\n", Translate2(DoWork()))
}

func DoWork() error {
	return &NotImplementedError{}
}

I wanted to share a deep-dive blog post, “The Perils of Pointers in the Land of the Zero-Sized Type”,
along with an accompanying new static analysis tool, zerolint:

Blog: “The Perils of Pointers in the Land of the Zero-Sized Type”

Repo: fillmore-labs.com/zerolint

I’ve found the posts Intriguing Pointer Equality Behavior on Zero-Width Types and Inconsistent behavior with pointers to empty struct here, but they are closed.

Do you have seen that issue in your own projects? Maybe you consider it a feature, not a bug? :upside_down_face:

Hello there. I think even in your blog you are missing one important detail. errors.Is is not what should’ve been used in this case, there is errors.As for this purpose.

errors.As is a workaround when you are stuck with pointers, but you still waste 8 bytes (on a 64 bit machine) for very little information.

Also, you run into the danger that someone might misuse your API and still uses errors.Is:

We live in a world where everything can and probably would be misused at some point. And go was never meant to be solver for a memory philosophical questions. 8 bytes is still nothing for something what will be GC-ed later

2 Likes