Assignment, reassignment, and shadowing in Go
Most people in Go are familiar with the basics of creating a variable and updating its value, but in some cases, it can become confusing even for engineers that are not new to Go. Let’s start with the basics. There are a few ways to declare and assign a variable:
Let’s focus on the operator
:= since it’s the one that can be tricky. As we’ve seen, it’s used to create a variable and assign a value at the same time. This means you cannot use it on a variable that is already declared… or can you?
As expected, this code won’t compile and throw the following error:
no new variables on left side of := , but let’s try something else:
In this example, everything compiles but might not produce the expected results. In this case, the output is
value in scope 2,
value in main 1 . The reason here is that
:= create a second variable
v . This is called shadowing. In any scope, you can shadow a symbol from the parent scope. The parent scope won’t be affected by the change. It’s usually highly recommended to avoid shadowing variables to prevent unwanted side effects. I have seen people shadowing variables by mistake multiple times, and this can lead to a headache when debugging. This is why I recommend people to use a linter on their editor/IDE that will let them know when they shadow a variable. You can do so using
go vet .
Now, let’s look at something a bit more confusing:
What should be the expected result here? We use
:= twice on
n so it should fail to compile, Right? Well… no, this code actually works. This is a common pattern in Go, but many people don’t really know what is actually happening. Is the variable
n shadowed? This is where
:= does a bit of magic. When you have multiple assignations at the same time, if at least one of the variables isn’t declared, Go will be smart enough to know if it needs to assign or declare:
This leads us to this example that confuses people. Look at the
Here it can be very confusing about what
close is actually getting. Especially because we’re using
:= everywhere. Now that we know how
:= works it does remove a bunch of questions. What do we know here?
- We know that
os.Openwill not create a new variable but assign a value to err.
&errwill send the address of
- The first
mayFailOrNot()will also assign a value to
err, so if it fails this is what
closewill get. We don’t need to provide a value to
returnbecause we’re using a named return value, so whatever is in
errat the time we do
returnis what will be returned.
- Now, what about the second
erris being shadowed so the
errwe have is a different variable, which means
close()should not get the value… but it does. Interesting.
- We then shadow
erragain, so based on the previous scope we now that it’s going to be caught by
close. Except it‘s not.
So why do the 2 scopes act differently even though they both shadow the same variable? The reason here is the
return statements. Remember,
err is declared in the signature of the method, which means that ultimately the final value of
err is the value we return. So whatever error we put in the return is what will be assigned to
err which is what will be caught by