C is one of the ancestors of Go, and Go inherits a lot of C syntax and expressions, including global variables, although Go does not directly give the definition of global variables in its syntax specification, but those who have already started Go know that the package exported variable in Go plays the role of a global variable. exported variables are similar to C global variables in terms of advantages, disadvantages, and usage.
I’m a C programmer, so I’m not a stranger to global variables, so I didn’t have much Gap when learning Go global variables, but people from other languages (like Java) may feel awkward when learning Go global variables, and they may not be able to understand how to use global variables for a long time.
In this post, let’s talk about global variables in Golang and understand them systematically with you.
I. Global Variables in Go
A global variable is a variable that can be accessed and modified throughout the program, regardless of where it is defined. Different programming languages have different ways of declaring and using global variables.
In Python, you can declare a global variable anywhere in the module. Like the globvar in the example below, but if you want to reassign it, you need to use the global keyword in the function.
There is no concept of global variables in Java, but you can use a public static variable of a class to simulate the effect of a global variable, because such a public class static variable can be accessed by any other class anywhere. For example, the following Java code for globalVar:
In Go, a global variable is an exported variable declared at the top level of a package with the first letter capitalized so that the variable can be accessed and modified anywhere in the entire Go program, such as the variable Global in the foo package in the following sample code.
foo.Global
can be read and modified by any other package that imports the foo package, just like those operations on it in code F1 above.
Even for global variables, the scope of the above global variables is package block, not universe block, according to the Go syntax specification.
Since Go exported variables act as global variables in Go, it has the same advantages and disadvantages as global variables in other languages. Let’s look at the advantages and disadvantages of global variables next.
II. Advantages and disadvantages of global variables
As the saying goes, if it exists, it exists for a reason! Let’s not discuss whether “existence is justified” is philosophically correct, but let’s first look at what benefits global variables can bring.
1. Advantages of global variables
-
First, global variables are easy to access.
The definition of a global variable dictates that it can be accessed anywhere in the program. Whether it is inside a function, a method, a loop, or a deeply indented conditional block, global variables can be accessed directly. This provides some “convenience” in reducing the number of arguments to a function, as well as eliminating the “hassle” of determining argument types and implementing argument passing.
Global variables can easily be accidentally modified or obscured by local variables, which can lead to unexpected problems.
-
Second, global variables are easy to share data with.
Due to their easy access, global variables are often used to share data between different parts of the program, such as configuration item data, command line flags, etc. Since the life cycle of global variables is the same as the whole life cycle of the program, they are not destroyed at the end of function calls and are not GC’d, but always exist and maintain their values. Therefore, when global variables are used as shared data, developers do not have the mental burden of worrying that the memory where the global variables are located has been “recycled”.
Concurrent goroutines need to consider the “data race” problem when accessing the same global variable.
-
Finally, global variables make the code look cleaner.
Go global variables only need to be declared once at the top level of the package, after which they can be accessed and modified from anywhere in the program. For the maintainer of the package in which the global variable is declared, this code couldn’t be more concise!
Code that accesses and modifies global variables in multiple places creates direct data coupling with global variables, reducing maintainability and extensibility.
In the above description, I wrote a “reverse” viewpoint for each of the advantages of global variables. These reverse arguments are clustered together to form the set of disadvantages of global variables, so let’s continue to look at them.
2. Disadvantages of global variables
-
First, global variables are easily modified accidentally or obscured by local variables.
As mentioned earlier, global variables are easily accessible, which means that all places may access or modify global variables directly. Changing a global variable in any one location may affect another function that uses it in an unexpected way. This makes testing against these functions more difficult. The presence of global variables makes isolation between tests poor, and if a global variable is modified during test case execution, it may be necessary to restore the global variable to its previous state before test execution ends to ensure the least possible interference with other test cases, as shown in the following example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
var globalVar int func F1() { globalVar = 5 } func F2() { globalVar = 6 } func TestF1(t *testing) { old := globalVar F1() // assert the result ... ... globalVar = old // Recover globalVar } func TestF2(t *testing) { old := globalVar F2() // assert the result ... ... globalVar = old // Recover globalVar }
In addition, global variables can easily be shadowed by local variables of the same name in functions, methods, and loops, leading to some strange and difficult debugging problems, especially when used in conjunction with Go’s short variable declaration syntax**.
go vet supports static analysis of code, but variable masking checks need to be installed additionally.
-
Second, there is a “data race” problem for accessing global variables under concurrency conditions.
If your program has multiple goroutines reading and writing global variables concurrently, then the “data contention” problem is inevitable. You need to protect global variables with additional synchronization means, such as mutual exclusion locks, read/write locks, atomic operations, etc.
By the same token, global variables that are not protected by synchronization means also limit the ability to execute unit tests in parallel (
-paralell
). -
Finally, while global variables bring code simplicity, more than anything else, they bring coupling that is detrimental to extension and reuse!
A global variable makes all code in the program that accesses and modifies it data-coupled to it, and small changes to the global variable will have an impact on that code. Thus, it would become very difficult to reuse or extend this code that depends on global variables. For example, if you want to parallelize their execution, you need to consider whether the global variables they are coupled to support synchronization means. To reuse the logic of this code in other programs, it may also be necessary to create a new global variable in the new program.
As we can see, Go global variables have advantages and a bunch of disadvantages, so how exactly should we treat global variables in the actual production coding process? Let’s move on to the next section.
III. Usage conventions and alternatives for Go global variables
How exactly does the Go language treat global variables? I looked through the standard library to see how the official Go team treats global variables, and I came to the conclusion that they should be used as little as possible.
There are “quite a few” global variables in the Go standard library, but most of them are global “sentinel” error variables, such as
|
|
These ErrXXX global variables are defined as “Variables (Var)”, but since Go has been open source for a long time, there is a tacit agreement that these ErrXXX variables are only “read-only” and no one will modify them in any way. Here some beginners may ask: Why not define them as constants? That’s because the only types of constants in Go are boolean constants, rune constants, integer constants, floating point constants, complex constants, and string constants.
No other types can be defined as constants. And the dynamic type returned by errors.New
is a pointer to the errors.errorString
structure type, which is also clearly outside the scope of constant types.
Apart from global variables like ErrXXX, there are very few other global variables in the Go standard library. A typical global variable is http.DefaultServeMux
:
The http
package is a highly used package carried by Go since its early days. I guess the global variable DefaultServeMux
was defined in the early implementation for some reason, and it may have been retained later for compatibility reasons, but in terms of code logic, removing it would not have any effect.
The logic of DefaultServeMux
, defaultServeMux
and NewServeMux
in the http package also shows that the Go language uses an alternative to global variables, which is “encapsulation”. ServeMux for example (let’s assume that the global variable DefaultServeMux
is removed and replaced by the package level unexported variable defaultServeMux
).
The http package defines the ServeMux type and the corresponding methods for handling multiplexing HTTP requests, but instead of directly defining a global variable for ServerMux (we assume that the DefaultServeMux
variable is removed), the http package defines a package level unexported variable defaultServeMux
as the default Mux.
The http package exports only two functions Handle and HandleFunc
for the caller to register the http request path with the corresponding handler (DefaultServeMux
can be replaced with defaultServeMux
in the following code):
|
|
This way http does not need to expose the details of the Mux implementation at all, and the caller does not need to rely on a global variable, a solution that converts the original data coupling to global variables into behavioral coupling to the http package.
A similar approach can be seen in the standard library log
package, which defines the package variable std
as the default logger, but only exposes a series of print functions such as Printf, whose implementation uses the corresponding methods of the package variable std
:
|
|
Note: Other languages may have some other alternatives to global variables, such as Java’s dependency injection.
IV. Summary
In summary, although global variables have the advantages of being easy to access, easy to share, and clean code, Go developers have chosen the best practice of “using global variables sparingly” compared to the disadvantages of accidental modification, concurrent data competition, and higher coupling.
In addition, the most common alternative to global variables in Go is encapsulation, which you can learn by reading the typical source code of the standard library.
V. Ref
https://tonybai.com/2023/03/22/global-variable-in-go/