user2502761
user2502761

Reputation: 563

When to use global variables

I've heard many times that you should avoid global variables.

In my example I declared a global myTypes variable only to avoid declaring that variable over and over again in a function call or something similar.

Is this how it should be done? Is there a better way? A more testable way?

var myTypes = map[string]string{
  "type1": "tpl1",
  "type2": "tpl2",
}

func AFunc(someType string) string {
  fmt.Sprintf("this is your type %s", myTypes[someType])
}

func main() {
  AFunc("type1")
}

Upvotes: 22

Views: 17010

Answers (3)

Olaf Klischat
Olaf Klischat

Reputation: 777

You're not modifying myTypes, just reading it, so it's not a variable at all, it's a constant, and would be defined as such if Go supported it (and make sure you don't mutate it -- unfortunately Go doesn't allow you to enforce "constness" like other languages do). Global constants are mostly fine.

If you were to modify myTypes, e.g. by providing a function to add new types at runtime, then yes, it's a bad idea to retain myTypes as global state. You might just get away with it as long as you do it only in your "main program" which you're sure will never be a package to be imported by other code, but you don't know where this code might end up / get copied to (or even just used from multiple places in the same package), so why risk it. If this becomes a package that gets imported by other packages, things may work fine as long as there's not more than one such "client" package is active at runtime, but as soon as somebody links together several such packages into one binary, all those user packages will stomp over each other's data in the global myTypes. If a client package expects to only see the myTypes that it put in earlier, this will break if there's another client with different expectations. So packages that work fine when used individually may break when used together, with no way to fix this except changing the shared code. So just don't do it. It's a shame that Google themselves use global state in some of their own public stuff, e.g. in the standard "flag" package and things like "glog" which uses it and thus inherits the problem. Don't do it.

Upvotes: 2

VonC
VonC

Reputation: 1326686

One usual way is to use Method Value

Consider a struct type T with two methods, Mv, whose receiver is of type T, and Mp, whose receiver is of type *T.

type T struct { 
    a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T

The expression

T.Mv

yields a function equivalent to Mv but with an explicit receiver as its first argument; it has signature

func(tv T, a int) int

You can see an example of Method Value in this thread

// TODO: Get rid of the global variable.
var foo service

func handleFoo(w http.ResponseWriter, req *http.Request) {
    // code that uses foo
}

func main() {
    foo = initFoo()

    http.HandleFunc("/foo", handleFoo)
}

One way to get rid of that global variable is to use method values:

type fooHandler struct {
    foo service
}

func (h fooHandler) handle(w http.ResponseWriter, req *http.Request) {
    // code that uses h.foo
}

func main() {
    foo := initFoo()

    http.HandleFunc("/foo", fooHandler{foo}.handle)
}

A new official approach for your global values is introduced in Go 1.7 with context.Context#Values.

Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.

See "How to correctly use context.Context in Go 1.7"


Finally, in addition of being hard to test, global values can prevent vendoring.

See "To vendor or not to vendor, that is a question"

Many Go’s libaries have exported package variables. Those variables can be viewed as certain global states of a certain package.

Prior vendoring era, we can go get each imported package once and the global state of each imported package can be shared within all other imported packages.

Some devs may take it as granted and simply manipulate those global states at will.
However, with vendoring each imported package may have its own view of global states. Now a dev may found it impossible to change other package’s view of global state

Upvotes: 15

Roland Illig
Roland Illig

Reputation: 41676

Not all global variables are bad. In your case:

  • The global variable is in the main package and therefore only accessible by a single program. This is ok.
  • The global variable is initialized once and not modified thereafter. This is ok.

On the other hand, whenever a global variable is modified during the program’s execution, the program becomes more difficult to understand. Therefore it should be avoided.

In a package that is meant to be reusable, global variables should be avoided since then two users of that package might influence each other. Imagine if the json package had a global variable var Indent bool. Such a variable is better to be hidden inside a data structure like JsonFormatter that gets created anew each time someone wants to format some JSON.

Upvotes: 16

Related Questions