Skip to content

Avoiding Go runtime errors with interface compliance and type assertion checks

Erica Pisani
Erica Pisani
2 min read

As I've been brushing up on my Go skills with a side project, one question I had was how errors related to structs not implementing an interface manifest in different ways in Go.

So here's a bit of a dive into what I learned on how we can ensure that the structs we define actually implement those interface methods so that the users of our software don't run into a runtime error later on.

Go makes a couple of mechanisms available to us for this. Specifically, Go has:

  1. compile-time interface compliance checks; and
  2. compile-time type assertions.

Which leads us to the question of what do these checks look like in practice?

Compile-time interface compliance check

This check will verify interface compliance when we attempt to use a struct where an interface is expected.

For instance, here's some code that would lead to a compile-time error as a result of an interface compliance check:

type SpecialDatabase interface {
  DoStuff() error
}

type Store struct {
    // Connection to a database
    db *pgx.Conn
}

func NewStore(ctx context.Context, connStr string) SpecialDatabase {
	db, err := pgx.Connect(ctx, connStr)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
		os.Exit(1)
	}

	return &Store{db: db}
}

This example doesn't follow the "accept interfaces, return concrete types" pattern for demonstration purposes

and would look something like the following:

cannot use &Store{…} (value of type *Store) as SpecialDatabase value in return statement: *Store does not implement SpecialDatabase (missing method DoStuff)

If you're writing Go code where your project is using the struct and methods defined on the interface, then this is likely the type of check that will lead to compile errors raising awareness of the issue.

Now that we know what interface compliance checks look like, let's look at the other mechanism that Go offers us.

Compile-time type assertion

This will verify interface compliance regardless of whether you're actually using the struct as a particular interface type anywhere in your code.

If the Go code being written is intended to be used as a library by others, then you'll likely want to use this type of assertion as the library may not be using the interface methods on the struct , yet those methods are expected to be defined on the struct by those using the library.

An example of some problematic code:

type Foo struct {
	Name string
}

type CoolInterface interface {
	DoStuff() error
}

func main() {
    test := Foo{Name: "Ada"}
	
    fmt.Printf("Hello %s", test.Name)
    
    test.DoStuff()
}

Without the type assertion, this will fail at runtime rather than compile-time with the following error:

test.DoStuff undefined (type Foo has no field or method DoStuff)

To ensure this is caught at compile-time, you'd need to write this type assertion:

var _ CoolInterface = (*Foo)(nil)

Let's break down this line of code a little further so we can understand what's going on:

  • (*Foo)(nil) - Creates a nil pointer to Foo. The nil value is used because we don't need an actual instance since we're just checking method signatures.
  • If Foo doesn't implement all methods in CoolInterface, you'll get a compile-time error like the following:   
cannot use (*Foo)(nil) (value of type *Foo) as CoolInterface value in variable declaration: *Foo does not implement CoolInterface (missing method DoStuff)

We also get the added benefit of this serving as documentation that the intention of the Foo struct is to have the methods specified on CoolInterface available on it.

Wrapping up

So to recap, if the Go program being written is intended to be used as a library, might want to consider adding type assertion checks to ensure errors are caught at compile time.

If the Go program being written uses the struct and invokes the methods defined on the interface, then type assertion checks are not entirely necessary as the interface compliance checks will likely catch these issues.

Happy coding!

golang

Comments