Some thought about `:=` while creating a new variable

Background

Yesterday, I wrote database driver part of my project as below

var (
  DBdriver = "mysql"
  DBinfo    = "xxx"
  DBengine *xorm.Engine
)

func init() {
  DBengine, err := xorm.NewEngine(DBdriver, DBinfo)
  ...
}

I thought that the DBengine variable has been declared globally and here even if err is a new variable, the compiler will assign the return result to the DBengine of the global variable. but in fact, since := is used, DBengine is also considered as a local variable for creation and assignment.

I ended up changing the code to

func init() {
  var err error
  DBengine, err = xorm.NewEngine(DBdriver, DBinfo)
  ...
}

Content

But I’m wondering why the compiler was designed to create DBengine as a local variable in such ambiguous cases, instead of prioritizing to see if a variable with the same name has already been declared and assigned to it. I guess, in terms of how the code is written, this just makes the coder create DBengine before this line if it needs to make it a local variable, which is not much different from my current solution; in terms of the logic of the code, this is more logical and beginner-friendly.

Can anyone explain why the compiler is designed this way and if I have a better way of coding

Hi @Augists,

The behavior is well-defined. Two aspects play a role here:

  1. The operator := always creates a new variable. There is no situation where it would try to find an existing variable in an outer scope and assign the value to that variable.

  2. A local variable always shadows a variable of the same name that is declared in an outer scope (including the global scope).

If this was not the case, code would be harder to read and easier to mess up.

Here is why: Consider that := would sometimes create a new local variable, and sometimes assign a value to a global variable. If you only look at the function that contains the := operation, you would not be able to tell whether := creates a new local variable or rather assigns a value to an existing global variable. (Consider that the global declaration could be dozens of lines above the function code, or even in a completely different file.) Accidentally assigning a value to a global variable rather than creating a local variable would just be too easy.

1 Like

A point that @christophberger does not touch on is that it can reuse variables in the local scope, idiomatically this would be the last variable such as err or ok:

  fooResult, err := foo()
  if err!=nil {
   log.Fatalf(err)
  }
  barResult, err := bar()
  ...

See here where it prints the address of reused variables and they don’t change. Go Playground - The Go Programming Language

If you move the declaration of fooResult to global scope, then it prints two different fooResult addresses.

I’m sure @christophberger knows all this, but for clarification for OP

1 Like

That is the core. Thanks!

So the complier will only search the local variable and assign if there is one named the same, or create a new one if have not been declared. I wrote some code and confirmed it. Here it is.

package main

import (
	"fmt"
)

func main() {
	test()
}

func test() {
	var foo int
	fmt.Println(&foo)
	foo, err := bar()
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(&foo)
}

func bar() (int, error) {
	return 1, nil
}

They print the same address as I expect. :smiley: