Language Design Lessons: Short Variable Declarations
Published: 2021-12-21 19:51:02

Please see [the series introduction](https://dev.to/djmitche/lessons-in-language-design-from-go-a-series-4g64) for context on this post.

---

I'll start this series off with something that every Go programmer is familiar with, even if it quickly fades "background noise" with daily use: `:=` and `=`.

# Short Variable Declarations Are (Almost) Redundant

The `:=` keyword is referred to as a "short variable declaration". It is governed by some [complex rules](https://golang.org/ref/spec#Short_variable_declarations) that dictate when it can and cannot be used.

I suspect that the `:=` vs. `=` distinction comes from a similar situation with C and C++, where a symbol's [_definition_ is distinct from its _declarations_](https://www.cprogramming.com/declare_vs_define.html).  This has implications for compiler complexity, as in many cases all it needs is a declaration in order to interact with a symbol.  A similar concern may have led to Go's use of this distinction, as compiler speed is an important Go feature.

In practice, as I am modifying code, this distinction generates a lot of noise.  For example, consider a function

```go
func doThings() error {
	x, err := thing1();
	if err != nil {
		return err
	}

	err = thing2(x);
	if err != nil {
		return err
	}

	// ...
}
```

If this function is modified to remove the call to `thing1`, the `=` in the call to `thing2` must be changed to `:=`. This is all the more annoying when doing something temporary like commenting out the call to `thing1` for a moment. In practice, I just add and remove `:` as the compiler instructs me until the code compiles.

Which means, the compiler can infer which form is correct in most cases. Could it do so in all cases? If so, could the language drop the construct or set up `gofmt` to automatically add or remove `:` as necessary?

## The Rules

Well, not quite. [Inanc Gumus](https://blog.learngoprogramming.com/golang-short-variable-declaration-rules-6df88c881ee) has a nice summary of the rules:

**You can't use it twice for the same variable**

```go
legal := 42
legal ?? 43
```

The `??` here must unambiguously be `=`.

**You can use them twice in multi-variable declarations(\*)** and **You can use them for multi-variable declarations and assignments**

```go
foo, bar := someFunc()
foo, bing ?? someFunc()
bar, bing ?? someFunc()
```

Again, the first `??` is completely specified: it must be `:=` because `bing` is new. The second is also clear: it must be `=` because neither variable is new.

**You can use them if a variable is already declared with the same name before** and **You can reuse them in scoped statement contexts like if, for, switch**

```go
var foo int = 34

func someFunc() {
	foo ?? 42
	// ...
}
```

This example is ambiguous. If `??` is replaced with `=`, then `someFunc` will modify the global `foo`. If `??` is replaced with `:=`, then `foo` is a local variable that [shadows](https://en.wikipedia.org/wiki/Variable_shadowing) the global `foo`, and the global will not be modified.

```go
foo := 34

if foo ?? aFunction(); foo == 34 {
	// ...
}
```

The situation is the same here: depending on the presence of those two little dots `:`, this will either modify or shadow the global `foo`.

## Shadowing

In general, shadowing is bad, as it can easily lead to difficult-to-detect errors, especially when moving code around. From a safety perspective, languages shouldn't allow programmers to do the sort of things that get programmers in trouble.

## What a Drag

The distinction between `:=` and `=` slows down the process of writing code.  Go programmers will be familiar with the situation: you decide to swap the order of two `error`-returning operations, and the editor lights up with a syntax error, leaving you to add and remove `:` characters until it's happy.  Or, you comment out a chunk of code for testing, and must make a half-dozen `:`-related changes below that point, only to revert them when the test is complete.

The drag is minor, to be sure, but these things add up over months and years.  And it's also true that automation could fix this.  For example, `gofmt` could insert or remove `:` as appropriate, and `govet` could warn about the ambiguous cases.  But this just adds additional churn to diffs with the appearance and disappearance of `:` characters, with no meaning for the human reader.

## Safety

Another consequence of this design decision is the risk of subtly incorrect code slipping by review, and even introducing security vulnerabilities.  Here's a particularly insidious case:

```go
func login(username) {
	var userID int

        if (remoteUserLookups) {
		userID, err := lookupUser(username)
		if err != nil { ... }
	}
}
```

Do you know what that `:=` does with `userID`? Are you sure? Would you catch it in review?  Could this confusion hide a security vulnerability?

The answer is that it makes a new `userID` that shadows the function-scope variable.  I expect this is why the [language spec](https://golang.org/ref/spec#Short_variable_declarations) refers to this as "redeclaring" when one of several variables to the left of `:=` is already defined. That's pretty subtle.

Even recognizing this subtlety, it takes more time and more thought for authors and reviewers to ensure the correctness of this code, further slowing development.

## Lessons

The two lessons I see here (and we'll see these recur in this blog series) are

 * requiring humans to think about unnecessary things slows development; and
 * subtle syntactic differences with substantial differences in meaning can lead to dangerous bugs.