头图

Unexpected variable hiding of 100 Go mistakes

雨阳
中文
This article is a translation of the book "100 Go Mistackes: How to Avoid Them". Due to the limited level of translation, it is inevitable that there will be translation accuracy problems, please understand

The scope of a variable refers to its visibility. In other words, where is the name in the program valid. In Go, variable names declared in blocks can be redeclared in internal blocks. This principle, called variable hiding, is prone to errors.

In the following example, we will see a bug related to variable hiding. We will create an HTTP client in two different ways, depending on the tracing boolean:

var client *http.Client    ①
if tracing {
    client, err := createClientWithTracing()    ②
    if err != nil {
        return err
    }
    log.Println(client)
}else {
    client, err := createDefaultClient()    ③
    if err != nil {
        return err
    }
    log.Println(client)
}

//use client

① Life a client variable
② Create an HTTP client with tracing, the client variable is hidden in this block
③ Create a default HTTP client, the client variable is still hidden in this module.

First, we declare a client variable. Then, in the two inner blocks, we use the := operator, also called the short variable declaration operator. This operator creates a new client variable with the same name as at the beginning; it does not assign a value to the client variable in line ①. Therefore, in this example, the HTTP client will always have a nil value.

Note: The code can be compiled successfully because the internal variable client is used in the logging call. Otherwise, we will have a compilation error: client declared and not used.

How do we ensure that the client is assigned? There are two different methods.
The first method is to use temporary variables in internal blocks, like the following:

var client *http.Client
if tracing {
    c, err := createClientWithTracing()    ①
    if err != nil {
        return err
    }
    client = c ②
} else {
    c, err := createDefaultClient()
    if err != nil {
        return err
    }
    client = c
}
// Use client

① Created a temporary variable c
② Assign temporary variables to the variable client
The life cycle of the variable c is only in the if/else block. Then, we assign these variables to the client.

The second way is to use the assignment operator (=) in the internal block to directly assign the return value of the function to the client variable. However, it needs to create an error variable, because the assignment operator only works when the variable has been declared.

var client *http.Client
var err error    ①
if tracing {
    client, err = createClientWithTracing() ②
    if err != nil {
        return err
    }
} else {
    client, err = createDefaultClient()
    if err != nil {
        return err
    }
}

① Declare variable err
② Use the assignment operator to directly assign the returned *http.Client to the client variable

In this example, we also assign the result of the internal call to the client. Which method is best? The first method is more convenient in most cases, but there is no compulsion to say which method to use.

When a variable name is redeclared in an internal block, variable hiding will happen. We have seen that this approach is prone to errors. Rules for avoiding hidden variables should be established based on the project and context. For example, sometimes it may be convenient to reuse existing variable names, like err errors. However, in general, we should be cautious, because we have seen that we may face such an error: the code can be compiled, but may not assign values to the variables we expect. Later in this chapter, we will see how to detect variable hiding to help us discover possible errors.

阅读 403
9 声望
1 粉丝
0 条评论
你知道吗?

9 声望
1 粉丝
宣传栏