04 Things About the Executable Programs Part 1

04 Things about the Executable Programs - Part 1 #

I have already opened the door to Go language programming for you and shown you the basic evolution path from building a program to splitting it up and then modularizing it.

For an experienced programmer, it may only take a few minutes or even a dozen minutes to complete the basic evolution of a program, because they already start with modular programming. I believe that once you truly understand this process, you will also become proficient at it.

The above approach is universal and not specific to the Go language. However, starting from this chapter, I will begin to introduce various features, programming methods, and concepts in Go language.


When I explained the two basic methods of writing source code files, I mentioned and used some program entities. You might have noticed them, or perhaps you are still confused. It doesn’t matter, I will now go through the key points with you.

Do you still remember? In Go language, program entities include variables, constants, functions, structures, and interfaces. Go language is a statically typed programming language, so when declaring variables or constants, we need to specify their types or provide enough information so that Go language can infer their types.

In Go language, the type of a variable can be one of its predefined types, or a user-defined function, structure, or interface. There are not many valid types for constants, they can only be the basic types predefined in Go language. The way to declare constants is also simpler.

Alright, now you need to understand the following simple question.

Question: What are the ways to declare variables? #

First, let’s take a look at the code snippet.

package main

import (
	"flag"
	"fmt"
)

func main() {
	var name string // [1]
	flag.StringVar(&name, "name", "everyone", "The greeting object.") // [2]
	flag.Parse()
	fmt.Printf("Hello, %v!\n", name)
}

This is a simple command source code file, which I named demo7.go. It is a modified version of demo2.go. I moved the declaration of the variable name and the call to the flag.StringVar function to the main function, corresponding to the comments [1] and [2] in the code.

The specific question is, are there any other ways to declare the variable name besides var name string? You can choose to modify the code at the comments [1] and [2] as appropriate.

Typical Answer #

There are several approaches, but here I will only mention the two most typical ones.

The first approach requires a slight modification to the code at the annotation [2]. The function being called needs to be changed from flag.StringVar to flag.String, and the list of arguments also needs to be modified accordingly. This is the preparation work for merging the code at [1] and [2].

var name = flag.String("name", "everyone", "The greeting object.")

The merged code looks cleaner. I removed the string in the code at annotation [1], added an =, and then concatenated it with the modified code at annotation [2].

Note that the return type of the flag.String function is *string, not string. The type *string represents a pointer to a string value, not a string itself. Therefore, the variable name here represents a pointer to a string value.

Regarding pointers in the Go language, I will have a dedicated introduction later. For now, all you need to know is that we can use the * operator to obtain the string value pointed to by this pointer. Therefore, in this case, the function call used for printing the content needs to be slightly adjusted, changing the argument name to *name, like this: fmt.Printf("Hello, %v!\n", *name).

Well, I think you have a basic understanding of each part of this line of code.

Now let me continue with the second approach. The second approach is very similar to the first approach. Based on the code in the first approach, we keep the code on the right side of the assignment operator =, remove everything on the left side except name, and change = to :=.

name := flag.String("name", "everyone", "The greeting object.")

Problem Analysis #

There are two basic points to consider in this question. One is that you need to know about type inference in the Go language, and how it is manifested in the code. The other is the usage of short variable declarations.

In the first way, the code declares the variable name and assigns a value to it, without explicitly specifying its type in the declaration.

Do you remember? The previous variable declaration statement was var name string. This utilizes the type inference feature of the Go language, thus eliminating the need to declare the type of the variable.

In simple terms, type inference is the ability of a programming language to automatically interpret the type of an expression during compilation. What is an expression? You can refer to the “Expressions” and “Expression Statements” chapters in the Go language specification for a detailed explanation. I won’t go into detail here.

You can think of the type of an expression as the type of the result obtained after evaluating the expression. Type inference in Go is very concise, which is also the overall style of the Go language.

It can only be used for variable or constant initialization, as described in the previous answer. The call to the flag.String function is actually a call expression, and the type of this expression is *string, which is a pointer type to a string.

This is also the type of the result obtained after calling the flag.String function. After that, Go directly uses the type of this expression that called the flag.String function as the type of the variable name, which is the operation referred to as “inference”.

As for the second way using a short variable declaration, it is essentially type inference in Go with a little bit of syntax sugar.

We can only use a short variable declaration inside a function body. When writing if, for, or switch statements, we often place it in the initialization clause to declare some temporary variables. In comparison, the first way is more general and can be used anywhere.

(Various ways to declare variables)

There are other ways to use short variable declarations, which I will explain later.

Knowledge Expansion #

1. What benefits does type inference in Go bring? #

If an interviewer asks you this question, how should you answer?

Certainly, when writing code, we can save a significant number of keystrokes by using type inference in Go. However, its real benefits often manifest in what happens after we write the code, such as code refactoring.

To demonstrate this better, we need to do some preparation work. We will still use a function call to declare the name variable and assign it a value, but this time the function will not be flag.String, but a function defined by ourselves, such as getTheFlag.

package main

import (
	"flag"
	"fmt"
)

func main() {
	var name = getTheFlag()
	flag.Parse()
	fmt.Printf("Hello, %v!\n", *name)
}

func getTheFlag() *string {
	return flag.String("name", "everyone", "The greeting object.")
}

We can wrap (or encapsulate) the call to the flag.String function with the getTheFlag function and use its result directly as the result of the getTheFlag function. The type of the result is *string.

With this change, the expression on the right side of var name = can be transformed into a call expression for the getTheFlag function. In fact, this is a refactoring of the line of code that declares and assigns the name variable.

We usually refer to the code modification method that “does not change any interaction methods and rules with the outside world of a program, but only changes its internal implementation” as refactoring of the program. The object of refactoring can be a line of code, a function, a feature module, or even a software system.

Now, after doing the preparation work, you will find that you can change the internal implementation of the getTheFlag function and the type of its return result at will without modifying any code in the main function.

This command source file can still be compiled, built, and run without any problems. Perhaps you can sense that this is a qualitative change in the flexibility of the program.

We do not explicitly specify the type of the name variable, allowing it to be assigned a value of any type. That is, the type of the name variable can be dynamically determined by other programs at its initialization.

After changing the result type of the getTheFlag function, Go’s compiler will automatically update the type of the name variable when you rebuild the program. If you have used dynamic typing programming languages like Python or Ruby, you will certainly find this situation familiar.

Yes, through this type inference, you can experience some of the advantages brought by dynamically typed programming languages, namely a significant increase in program flexibility. However, in those programming languages, this improvement can be said to come at the cost of program maintainability and runtime efficiency. Go language is statically-typed, so once the type of a variable is determined during initialization, it cannot be changed afterwards. This avoids some problems when maintaining the program later on. Also, please note that this type determination is done at compile time, so it does not have any impact on the runtime efficiency of the program.

By now, you should have a fairly deep understanding of this topic.

If I were to answer this question in just one or two sentences, it would be like this: Type inference in Go language can significantly improve the flexibility of the program, making code refactoring easier without adding any extra burden for code maintenance (in fact, it can prevent shotgun-style code modifications), and without sacrificing runtime efficiency of the program.

2. What does variable redeclaration mean? #

This involves short variable declaration. By using it, we can redeclare variables within the same code block.

Since we mentioned code blocks, let me explain it first. In Go language, a code block is generally an area enclosed by curly braces, which can contain expressions and statements. Go language itself and the code we write together form a very large code block, also known as the global code block.

This is mainly reflected in the fact that any public global variable can be used by any code. Relatively smaller code blocks are code packages, and a code package can contain many sub-packages, so such code blocks can also be large.

Next, each source code file is also a code block, each function is also a code block, each if statement, for statement, switch statement, and select statement is also a code block. Even the case clauses in a switch or select statement are independent code blocks.

To go to the extreme, can I consider a pair of adjacent curly braces in the main function as a code block? Of course, it also counts, there is even a term for it, called “empty code block”.

Back to the question of variable redeclaration. It means redeclaring a variable that has already been declared. The prerequisites for variable redeclaration are as follows.

  1. Since the type of a variable is determined during its initialization, when redeclaring it, the assigned type must be the same as its original type, otherwise a compilation error will occur.

  2. Variable redeclaration can only occur within a code block. If the variable with the same name is in an outer code block, then it has a different meaning, but I will talk about it in the next article.

  3. Variable redeclaration can only occur when using short variable declaration, otherwise it cannot be compiled. If you want to declare a completely new variable at this point, you should use a declaration statement that includes the keyword var, but then you cannot have the same name as any variable in the same code block.

  4. The variables being “declared and assigned” must be multiple, and at least one of them must be a new variable. Only then can we say that the old variables have been redeclared.

In this sense, variable redeclaration is actually a syntactic sugar (or convenience measure). It allows us to not worry about whether the variables being assigned contain old variables when using short variable declaration. You can imagine that without this, we would have to write a lot more code.

I have written a simple example in the puzzlers/article4/q3 package of the “Golang_Puzzlers” project, you can take a look.

The two most important lines of code are as follows:

var err error
n, err := io.WriteString(os.Stdout, "Hello, everyone!\n")

I use short variable declaration to “declare and assign” the new variable n and the old variable err, which is also a redeclaration of the latter.

Summary #

In this article, we focused on the most basic entity in Go language programs: variables. We explained in detail the basic methods of variable declaration and assignment, as well as the important concepts and knowledge behind them. We can use the keyword var or the short variable declaration to achieve “declaration and assignment” of variables.

Both methods have their own advantages and characteristics, with different scenarios where they are applicable. The former can be used anywhere, while the latter can only be used within functions or other smaller code blocks.

However, with the former method, we cannot redeclare existing variables, which means it cannot handle situations where new and old variables are mixed together. However, they also have an important common point: based on type inference, Go language’s type inference is only applied to the initialization of variables or constants.

Thought Question #

There is only one thought question for this time: What does it mean if a variable has the same name as a variable in the outer code block?

This question may be challenging for you, but I encourage you to conduct several experiments. You can add some print statements to the code, run it, and record the results of each experiment. If you have any questions, be sure to write them down. The answer will be revealed in the next article.

Click here to view the detailed code accompanying the Go Language column article.