We've mentioned the blank identifier a couple of times now, in the context of for range loops and maps. The blank identifier can be assigned or declared with any value of any type, with the value discarded harmlessly. It's a bit like writing to the Unix /dev/null file: it represents a write-only value to be used as a place-holder where a variable is needed but the actual value is irrelevant. It has uses beyond those we've seen already.
我们已经在 For 循环 和 maps 中两次提到空白标识符了。空白标识符可以用任意类型或任意值来声明和赋值,但其值会被丢弃,然而这并没有什么影响。就好像往 Unix 文件 /dev/null 中写内容一样:它表示一个只写的占位符,用在需要变量但实际值却无关紧要之处。除此之外,空白标识符尚有其它妙用。
The use of a blank identifier in a for range loop is a special case of a general situation: multiple assignment.
for 循环中使用空白标识符仅仅是多重赋值的一个特例。
If an assignment requires multiple values on the left side, but one of the values will not be used by the program, a blank identifier on the left-hand-side of the assignment avoids the need to create a dummy variable and makes it clear that the value is to be discarded. For instance, when calling a function that returns a value and an error, but only the error is important, use the blank identifier to discard the irrelevant value.
如果赋值语句左侧需要多个值,但其中一个将来并不会使用,那么在左侧使用空白标识符可以避免创建虚假变量,并且可清晰地表明变量被丢弃的意图。例如,当调用函数时返回一个值和一个 error,但是仅 error 是重要的,此时即可使用空白标识符来丢弃无关紧要的值。
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("%s does not exist\n", path)
}
Occasionally you'll see code that discards the error value in order to ignore the error; this is terrible practice. Always check error returns; they're provided for a reason.
有时你会看到为了忽略错误而丢弃 error 的代码;这是个可怕的做法。记住要永远检查错误,事出必有因!
// Bad! This code will crash if path does not exist.
fi, _ := os.Stat(path)
if fi.IsDir() {
fmt.Printf("%s is a directory\n", path)
}
It is an error to import a package or to declare a variable without using it. Unused imports bloat the program and slow compilation, while a variable that is initialized but not used is at least a wasted computation and perhaps indicative of a larger bug. When a program is under active development, however, unused imports and variables often arise and it can be annoying to delete them just to have the compilation proceed, only to have them be needed again later. The blank identifier provides a workaround.
导入一个包或者声明一个变量却不使用是非法的。未使用的导入会使程序膨胀且拖慢编译速度,而一个未使用的初始化变量至少会浪费算力,或者是一个更大 bug 的迹象。然而,当一个项目处于活跃开发中时,未使用的导入和变量常有发生,而且仅仅为了编译通过将其删除,结果却在不久之后又需要它们,这非常令人讨厌。空白标识符提供了一种变通。
This half-written program has two unused imports (fmt and io) and an unused variable (fd), so it will not compile, but it would be nice to see if the code so far is correct.
下面这段未完成的程序有两个未使用的导入(fmt 和 io)和一个未使用的变量(fd),因此它无法编译,但要是能验证一下目前代码的正确性将会非常不错。
package main
import (
"fmt"
"io"
"log"
"os"
)
func main() {
fd, err := os.Open("test.go")
if err != nil {
log.Fatal(err)
}
// TODO: use fd.
}
To silence complaints about the unused imports, use a blank identifier to refer to a symbol from the imported package. Similarly, assigning the unused variable fd to the blank identifier will silence the unused variable error. This version of the program does compile.
使用空白标识符引用一个导入包中的符号可以绕过编译器的检查。同样,将一个变量赋值给空白标识符会消除未使用变量的错误。下面这个版本可以编译通过。
package main
import (
"fmt"
"io"
"log"
"os"
)
var _ = fmt.Printf // For debugging; delete when done.
var _ io.Reader // For debugging; delete when done.
func main() {
fd, err := os.Open("test.go")
if err != nil {
log.Fatal(err)
}
// TODO: use fd.
_ = fd
}
By convention, the global declarations to silence import errors should come right after the imports and be commented, both to make them easy to find and as a reminder to clean things up later.
依照惯例,用于消除导入错误的全局声明应该紧跟在导入之后,并加以注释,这样既便于查找,又可以提醒您稍后进行清理。
An unused import like fmt
or io
in the previous example should eventually be used or removed: blank assignments identify code as a work in progress. But sometimes it is useful to import a package only for its side effects, without any explicit use. For example, during its init
function, the net/http/pprof
package registers HTTP handlers that provide debugging information. It has an exported API, but most clients need only the handler registration and access the data through a web page. To import the package only for its side effects, rename the package to the blank identifier:
前述案例中未使用的包,比如 fmt
或 io
最终要么被使用要么被删除:空白赋值表明工作尚在进行中。不过,有时候我们仅仅只是为了副作用而导入一个包,并不会真正的使用它,这是非常有用的。例如,net/http/pprof
包在其 init 函数中注册了提供调试信息的 HTTP handlers
。虽然它有一些导出的 API,但大多数客户端只需要 handler 注册后通过网页来访问数据。将导入的包重命名为空白标识符,即可使副作用生效:
import _ "net/http/pprof"
This form of import makes clear that the package is being imported for its side effects, because there is no other possible use of the package: in this file, it doesn't have a name. (If it did, and we didn't use that name, the compiler would reject the program.)
这种导入方式明确地表明是为了副作用而导入的,因为该包没有其它可能的用途:在此文件中,它没有自己的名字。(如果它有,而我们却没使用它,编译器将会报错。)
As we saw in the discussion of interfaces above, a type need not declare explicitly that it implements an interface. Instead, a type implements the interface just by implementing the interface's methods. In practice, most interface conversions are static and therefore checked at compile time. For example, passing an *os.File
to a function expecting an io.Reader
will not compile unless *os.File
implements the io.Reader
interface.
如前所述,一个类型无需显示地声明其要实现的接口。相反,只需要实现接口中的方法即可。实践中,大多数的接口转换都是静态的,故在编译器即可进行检查。例如,向一个参数为 io.Reader
的函数传递 *os.File
,如果 *os.File
未实现 io.Reader
接口,将导致编译失败。
Some interface checks do happen at run-time, though. One instance is in the encoding/json
package, which defines a Marshaler
interface. When the JSON encoder receives a value that implements that interface, the encoder invokes the value's marshaling method to convert it to JSON instead of doing the standard conversion. The encoder checks this property at run time with a type assertion like:
然而,有些接口检查却发生在在运行时。encoding/json
包中就有一个例子,它定义了 Marshaler
接口。当接收到一个实现了该接口的值时,JSON encoder
不会进行标准转换,而是调用该值的 marshaling 方法来转换成 JSON
。JSON encoder
在运行时利用断言来检查,就像这样:
m, ok := val.(json.Marshaler)
If it's necessary only to ask whether a type implements an interface, without actually using the interface itself, perhaps as part of an error check, use the blank identifier to ignore the type-asserted value:
若只想检查一个类型是否实现了接口,而不需要实际使用接口的话(可能错误检查时需要),使用空白标识符来忽略断言的值:
if _, ok := val.(json.Marshaler); ok {
fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}
One place this situation arises is when it is necessary to guarantee within the package implementing the type that it actually satisfies the interface. If a type—for example, json.RawMessage
—needs a custom JSON representation, it should implement json.Marshaler
, but there are no static conversions that would cause the compiler to verify this automatically. If the type inadvertently fails to satisfy the interface, the JSON encoder will still work, but will not use the custom implementation. To guarantee that the implementation is correct, a global declaration using the blank identifier can be used in the package:
其中一个使用场景是:在实现类型的包中,当我们有必要确保它真正满足接口的时候。举个例子,json.RawMessage
需要自定义的 JSON 表示,那么它应该实现 json.Marshaler
接口,但是这里并没有静态转换使得可以利用编译器来自动验证。为确保类型正确实现接口,可在包内声明一个全局的空白标识符:
var _ json.Marshaler = (*RawMessage)(nil)
In this declaration, the assignment involving a conversion of a *RawMessage
to a Marshaler
requires that *RawMessage
implements Marshaler
, and that property will be checked at compile time. Should the json.Marshaler
interface change, this package will no longer compile and we will be on notice that it needs to be updated.
在此声明中,赋值包含了一个 *RawMessage
到 Marshaler
的转换,这需要 *RawMessage
实现 Marshaler
接口。如此一来,就可以在编译期得到保证。如果接口改变了,该包就不会通过编译,我们也将会注意到此,并及时更新 *RawMessage
的实现。
The appearance of the blank identifier in this construct indicates that the declaration exists only for the type checking, not to create a variable. Don't do this for every type that satisfies an interface, though. By convention, such declarations are only used when there are no static conversions already present in the code, which is a rare event.
在上述设想中空白标识符的出现,表示此声明仅服务于类型检查,并不创建变量。然而,不要为了接口检查而滥用。按照惯例,此类声明只适用于没有静态转换的代码中,而这种情况并不常见。