Demystifying 'typechecking loop' errors in Go

A recent addition to the Pages product, Screens is a mobile store locator SDK with a server-side API written in Go. In keeping with our philosophy of full-stack teams, this gave me an opportunity to branch out from the mobile side of things and get my hands dirty with a brand new language.

Moving to a new programming language can be frustrating at times: trying to re-mold your brain into a new way of thinking, finding alien items of syntax just when you thought you were getting the hang of it, and dealing with compiler errors that don’t quite give enough of a hint to the newbie.

The Problem

Having only recently started working in Go, I fell afoul of the third problem just yesterday, when I saw the following during a build:

typechecking loop involving cache

The offending line being:

var cache *cache.Cache = cache.New(...)

The Cause

When trying to evaluate the problem line, the compiler gets confused as to the difference between the variable named cache and the package named cache. While this could potentially make for less readable code, in many cases it wouldn’t cause an error like this. In fact, this particular error only appeared when the cache variable was declared in package scope.

As a result, there’s not a whole lot of help available regarding this error when it does crop up, and a number of cases where it used to appear have long since had more friendly messages put in place.

Unlike other languages in the C tradition, in Go the order in which package level variables are declared doesn’t actually matter. This means you can do things like this:

type Foo struct {
	bar string

var foo *Foo = &Foo{bar: myBar}
var myBar = "Yep, this is totally OK"

This is a pretty neat feature, but in our broken program, this means that the compiler thinks that cache.New(...) refers to the cache variable that we’re declaring in the same line, not the cache package. So the compiler expects that cache should have the same type as itself, and we have our typechecking loop.


The solution is pretty straightforward, just change the name of the cache variable:

var myCache *cache.Cache = cache.New(...)

Alternatively, you could alias the package import:

import c ""

var myCache *c.Cache = c.New(...)

Aliasing the package can make the code more difficult to follow, so we went with the first approach.

More Complex Examples

Diving into the source code for go, a quick search for “typechecking loop” brings up gc.typecheck, which errors out if a node appears in its own typechecking chain (meaning that the node can only be typechecked by typechecking itself). So in addition to naming clashes, we would also encounter a loop when we have circular references.

Let’s have a look at this issue for the circular reference case. The example in the issue wraps the problem variables in arrays, but that’s not actually necessary to trigger the error, so we can reduce it to the below:

package main

import "fmt"

type Foo struct {
	pbar *Bar

type Bar struct {
	pfoo *Foo

var b = Bar{pfoo: &f}
var f = Foo{pbar: &b}

func main() {
	fmt.Println(f, b)

This results in a typechecking loop because we need to know the type of f in order to check the type of b, and we need to know the type of b to check the type of f. If we try to be explicit about the type of b and f:

var b *Bar = Bar{pfoo: &f}
var f *Foo = Foo{pbar: &b}

We get an “initialization loop” error instead, which makes sense, since to initialize b, we need f and vice versa.


Should you encounter this error message in the wild, checking for naming clashes is a good first step. Beyond that, try drawing a graph of references from the node mentioned in the error, and see if you can find a cycle.