Go Experience report: Go’s type system

When Go’s type system impedes library interoperability

Jack Lindamood
Sep 7, 2017 · 3 min read
A gopher tries to drink coffee

What I wanted to do

Today’s pkg/errors

type StackTrace []Frame
type Frame uintptr
type stackTracer interface {
StackTrace() errors.StackTrace
}

Simultaneously to this, there exist libraries that would like to extract stack frame information from errors, if it’s present. One example is the rollbar logging library, which allows logging errors with the stack trace that created them. The only way for a rollbar logging library to support pkg/errors is to import the library directly and type assert for a StackTrace function.

import “github.com/pkg/errors”type stackTracer interface {
StackTrace() errors.StackTrace
}
func logError(err error) {
if s, ok := err.(stackTracer); ok {
// …
}
}

To resolve some issues with pkg/errors I decided to fork it. Because of stackTracer’s implementation, my forked StackTrace function will not resolve when type asserted. Not only is my fork unsupported by the rollbar library, but I’m also forced to vendor and manage dependencies to pkg/errors.

What I actually did

Forking pkg/errors

import errors1 “github.com/pkg/errors”
import errors2 “github.com/cep21/errors”
type stackTracer1 interface {
StackTrace() errors1.StackTrace
}
type stackTracer2 interface {
StackTrace() errors2.StackTrace
}
func logError(err error) {
if s, ok := err.(stackTracer1); ok {
// …
}
if s, ok := err.(stackTracer2); ok {
// …
}
}

It’s easy to see how silly this code could become if scaled to multiple packages. In order to support both implementations, I just forked the rollbar logging library as well and modified the import to my fork, rather than pkg/errors.

Alternative pkg/errors

type stackTracer interface {
StackTrace() []uintptr
}
func logError(err error) {
if s, ok := err.(stackTracer); ok {
// …
}
}

This puts no burden on the user of the library to manage dependencies to pkg/errors. The downside is that pkg/errors has now lost type information, or custom functions, on stack trace items.

Why that wasn’t great

type StackTrace interface {
Stack() []uintptr
}
type stackTracer interface {
StackTrace() StackTrace
}
func logError(err error) {
if s, ok := err.(stackTracer); ok {
// …
}
}

If Go’s type system was flexible enough to allow rollbar to indicate it only required StackTrace().Stack() behavior, it could support multiple forks of the errors package without requiring a dependency.

Statuscode

Keeping developers informed.

Jack Lindamood

Written by

Software Engineer

Statuscode

Keeping developers informed.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade