From 9e329a0d16a053a884ef02d4e17a50cafea61afc Mon Sep 17 00:00:00 2001
From: Rob Pike
+The blank identifier has many uses, as described in a later section.
+
For strings, the
A switch can also be used to discover the dynamic type of an interface
variable. Such a type switch uses the syntax of a type
@@ -1556,11 +1561,8 @@ func offset(tz string) int {
To test for presence in the map without worrying about the actual value,
-you can use the blank identifier (range
does more work for you, breaking out individual
Unicode code points by parsing the UTF-8.
@@ -802,6 +805,8 @@ func Compare(a, b []byte) int {
}
+Type switch
+
_
).
-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.
-For testing just presence in a map, use the blank
-identifier in place of the usual variable for the value.
+you can use the blank identifier (_
)
+in place of the usual variable for the value.
_, present := timeZone[tz]
@@ -2312,6 +2314,196 @@ a channel, and a function, all because interfaces are just sets of
methods, which can be defined for (almost) any type.
+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.
+
+The use of a blank identifier in a for
range
loop is a
+special case of a general situation: multiple assignment.
+
+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 +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. +
+ ++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. +
+ ++// 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. +
+
+This half-written program is 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.
+
+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.
+
+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:
+
+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.
+
+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 code like:
+
+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 it actually satisfies the interface.
+If a type—for example,
+json.RawMessage
—needs
+a custom its 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:
+
+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.
+
+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. +
+ +@@ -3146,155 +3338,6 @@ filter unexpected problems and re-panic with the original error. That's left as an exercise for the reader.
-
-Go defines a special identifier _
, called the blank identifier.
-The blank identifier can be used in a declaration to avoid
-declaring a name, and it can be used in an assignment to discard a value.
-This definition makes it useful in a variety of contexts.
-
-If an assignment requires multiple values on the left side, -but one of the values will not be used by the program, -using the blank identifier in the assignment avoids the need -to create a dummy variable. -We saw one example of this in the discussion of -for loops above. -
--sum := 0 -for _, value := range array { - sum += value -} -- -
-Another common use is when calling a function that returns -a value and an error, but only the error is important. -
--if _, err := os.Stat(path); os.IsNotExist(err) { - fmt.Printf("%s does not exist\n", path) -} -- -
-A final use that is more common than it should be is to -discard the error from a function that is not expected to fail. -This is usually a mistake: when the function does fail, the code -will continue on and probably panic dereferencing a nil pointer. -
--// Always check errors: this program crashes if path does not exist. -fi, _ := os.Stat(path) -fmt.Printf("%s is %d bytes\n", path, fi.Size()) -- -
-Go defines that it is an error to import a package without using it, -or to declare a variable without using its value. -Unused imports bloat a program and lengthen compiles unnecessarily; -a variable that is initialized but not used is at least -a wasted computation and perhaps indicative of a -larger bug. -Of course, both of these situations also arise in programs -that are under active development, as you test and refine -your code. -
-
-For example, in this program, there are two unused imports
-(fmt
and io
)
-and an unused variable (greeting
).
-
-Top-level blank declarations referring to the packages
-will silence the unused import errors.
-By convention, these declarations should come immediately after
-the imports, as a reminder to clean things up later.
-Similarly, assigning greeting
to a blank identifier
-will silence the unused variable error.
-
-An unused import like fmt
or io
in the last section
-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 useful
-debugging information. It has an exported API too, but
-most clients need only the handler registration.
-In this situation, it is conventional to rename the package
-to the blank identifier:
-
-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. -
- -
-As we saw in the discussion of interfaces above,
-Go does not require a type to declare explicitly that it implements an interface.
-It implements the interface by simply implementing the required methods.
-This makes Go programs more lightweight and flexible, and it can avoid
-unnecessary dependencies between packages.
-Most interface conversions are static, visible to the compiler,
-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.
-
-However, some types that are used only to satisfy dynamic interface checks.
-For example, the encoding/json
-package defines a Marshaler
-interface. If the JSON encoder encounters a type implementing that interface,
-the encoder will let the type convert itself to JSON instead of using the standard
-conversion.
-This check is done only at runtime, with code like:
-
-m, ok := val.(json.Marshaler) --
-If a type—for example,
-json.RawMessage
—intends
-to customize its JSON representation, it should implement
-json.Marshaler
, but there are no static conversions that would
-cause the compiler to verify this automatically.
-A declaration can be used to add such a check:
-
-var _ json.Marshaler = (*RawMessage)(nil) --
-As part of type-checking this static assignment of a
-*RawMessage
to a Marshaler
,
-the Go compiler will require that *RawMessage
implements Marshaler
.
-Using the blank identifier here indicates that
-the declaration exists only for the type checking,
-not to create a variable.
-Conventionally, such declarations are used only when there are
-no static conversions already present in the code.
-