From 9e329a0d16a053a884ef02d4e17a50cafea61afc Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Fri, 8 Mar 2013 10:41:20 -0800 Subject: [PATCH] effective_go.html: move and rework the blank identifier section Also rename the relevant examples and make sure the working one compiles. R=golang-dev, bradfitz, adg, iant, rsc CC=golang-dev https://golang.org/cl/7597043 --- doc/effective_go.html | 351 ++++++++++++++++++++++----------------- doc/progs/eff_unused1.go | 18 ++ doc/progs/eff_unused2.go | 22 +++ doc/progs/run | 1 + doc/progs/unused1.go | 12 -- doc/progs/unused2.go | 16 -- 6 files changed, 238 insertions(+), 182 deletions(-) create mode 100644 doc/progs/eff_unused1.go create mode 100644 doc/progs/eff_unused2.go delete mode 100644 doc/progs/unused1.go delete mode 100644 doc/progs/unused2.go diff --git a/doc/effective_go.html b/doc/effective_go.html index 0986e538494..570ca052347 100644 --- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -695,6 +695,9 @@ for _, value := range array { } +

+The blank identifier has many uses, as described in a later section. +

For strings, the 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

+

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 (_). -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.
 

+

The blank identifier

+ +

+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 blank identifier in multiple assignment

+ +

+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)
+}
+
+ +

Unused imports and variables

+ +

+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. +

+{{code "/doc/progs/eff_unused1.go" `/package/` `$`}} +

+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. +

+{{code "/doc/progs/eff_unused2.go" `/package/` `$`}} + +

+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. +

+ +

Import for side effect

+ +

+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.) +

+ +

Interface checks

+ +

+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. +

+ +

Embedding

@@ -3146,155 +3338,6 @@ filter unexpected problems and re-panic with the original error. That's left as an exercise for the reader.

-

Blank identifier

- -

-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. -

- -

Multiple assignment

- -

-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())
-
- -

Unused imports and variables

- -

-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). -

-{{code "/doc/progs/unused1.go" `/package/` `$`}} -

-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. -

-{{code "/doc/progs/unused2.go" `/package/` `$`}} - -

Import for side effect

- -

-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. -

- -

Interface checks

- -

-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. -

A web server

diff --git a/doc/progs/eff_unused1.go b/doc/progs/eff_unused1.go new file mode 100644 index 00000000000..f990a19f77a --- /dev/null +++ b/doc/progs/eff_unused1.go @@ -0,0 +1,18 @@ +// skip + +package main + +import ( + "fmt" + "io" + "log" + "os" +) + +func main() { + fd, err := os.Open("test.go") + if err != nil { + log.Fatal(err) + } + // TODO: use fd. +} diff --git a/doc/progs/eff_unused2.go b/doc/progs/eff_unused2.go new file mode 100644 index 00000000000..3e6e041c76e --- /dev/null +++ b/doc/progs/eff_unused2.go @@ -0,0 +1,22 @@ +// 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 +} diff --git a/doc/progs/run b/doc/progs/run index da777f329be..71759c565eb 100755 --- a/doc/progs/run +++ b/doc/progs/run @@ -16,6 +16,7 @@ effective_go=" eff_bytesize eff_qr eff_sequence + eff_unused2 " error_handling=" diff --git a/doc/progs/unused1.go b/doc/progs/unused1.go deleted file mode 100644 index 96a6d98a393..00000000000 --- a/doc/progs/unused1.go +++ /dev/null @@ -1,12 +0,0 @@ -// skip - -package main - -import ( - "fmt" - "io" -) - -func main() { - greeting := "hello, world" -} diff --git a/doc/progs/unused2.go b/doc/progs/unused2.go deleted file mode 100644 index 5c5f9d74f49..00000000000 --- a/doc/progs/unused2.go +++ /dev/null @@ -1,16 +0,0 @@ -// compile - -package main - -import ( - "fmt" - "io" -) - -var _ = fmt.Printf -var _ io.Reader - -func main() { - greeting := "hello, world" - _ = greeting -}