From 49d82b4ca1a902f5667e845e82440c83287ee633 Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Mon, 12 Dec 2011 13:15:29 +1100 Subject: [PATCH] doc: add Defer, Panic, and Recover article Originally published on The Go Programming Language Blog, August 4 2010. http://blog.golang.org/2010/08/defer-panic-and-recover.html Update #2547 R=golang-dev, r, r CC=golang-dev https://golang.org/cl/5479053 --- doc/Makefile | 2 +- doc/articles/defer_panic_recover.html | 274 ++++++++++++++++++++++++++ doc/articles/defer_panic_recover.tmpl | 193 ++++++++++++++++++ doc/makehtml | 4 +- doc/progs/defer.go | 53 +++++ doc/progs/defer2.go | 56 ++++++ doc/progs/run | 52 +++-- doc/tmpltohtml.go | 5 +- 8 files changed, 616 insertions(+), 23 deletions(-) create mode 100644 doc/articles/defer_panic_recover.html create mode 100644 doc/articles/defer_panic_recover.tmpl create mode 100644 doc/progs/defer.go create mode 100644 doc/progs/defer2.go diff --git a/doc/Makefile b/doc/Makefile index 4e8ba08c175..f65e538d974 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -8,7 +8,7 @@ TARG=tmpltohtml GOFILES=\ tmpltohtml.go\ -all: tmpltohtml go_tutorial.html effective_go.html go1.html +all: tmpltohtml go_tutorial.html effective_go.html go1.html articles/defer_panic_recover.html %.html: %.tmpl tmpltohtml ./makehtml $*.tmpl diff --git a/doc/articles/defer_panic_recover.html b/doc/articles/defer_panic_recover.html new file mode 100644 index 00000000000..06f7685d48a --- /dev/null +++ b/doc/articles/defer_panic_recover.html @@ -0,0 +1,274 @@ + + +

+Go has the usual mechanisms for control flow: if, for, switch, goto. It also +has the go statement to run code in a separate goroutine. Here I'd like to +discuss some of the less common ones: defer, panic, and recover. +

+ +

+A defer statement pushes a function call onto a list. The list of saved +calls is executed after the surrounding function returns. Defer is commonly +used to simplify functions that perform various clean-up actions. +

+ +

+For example, let's look at a function that opens two files and copies the +contents of one file to the other: +

+ +
func CopyFile(dstName, srcName string) (written int64, err error) {
+    src, err := os.Open(srcName)
+    if err != nil {
+        return
+    }
+
+    dst, err := os.Create(dstName)
+    if err != nil {
+        return
+    }
+
+    written, err = io.Copy(dst, src)
+    dst.Close()
+    src.Close()
+    return
+}
+
+ +

+This works, but there is a bug. If the second call to os.Open fails, the +function will return without closing the source file. This can be easily +remedied by putting a call to src.Close() before the second return statement, +but if the function were more complex the problem might not be so easily +noticed and resolved. By introducing defer statements we can ensure that the +files are always closed: +

+ +
func CopyFile(dstName, srcName string) (written int64, err error) {
+    src, err := os.Open(srcName)
+    if err != nil {
+        return
+    }
+    defer src.Close()
+
+    dst, err := os.Create(dstName)
+    if err != nil {
+        return
+    }
+    defer dst.Close()
+
+    return io.Copy(dst, src)
+}
+
+ +

+Defer statements allow us to think about closing each file right after opening +it, guaranteeing that, regardless of the number of return statements in the +function, the files will be closed. +

+ +

+The behavior of defer statements is straightforward and predictable. There are +three simple rules: +

+ +

+1. A deferred function's arguments are evaluated when the defer statement is +evaluated. +

+ +

+In this example, the expression "i" is evaluated when the Println call is +deferred. The deferred call will print "0" after the function returns. +

+ +
func a() {
+    i := 0
+    defer fmt.Println(i)
+    i++
+    return
+}
+
+ +

+2. Deferred function calls are executed in Last In First Out order +after the surrounding function returns. +

+ +

+This function prints "3210": +

+ +
func b() {
+    for i := 0; i < 4; i++ {
+        defer fmt.Print(i)
+    }
+}
+
+ +

+3. Deferred functions may read and assign to the returning function's named +return values. +

+ +

+In this example, a deferred function increments the return value i after +the surrounding function returns. Thus, this function returns 2: +

+ +
func c() (i int) {
+    defer func() { i++ }()
+    return 1
+}
+
+ +

+This is convenient for modifying the error return value of a function; we will +see an example of this shortly. +

+ +

+Panic is a built-in function that stops the ordinary flow of control and +begins panicking. When the function F calls panic, execution of F stops, +any deferred functions in F are executed normally, and then F returns to its +caller. To the caller, F then behaves like a call to panic. The process +continues up the stack until all functions in the current goroutine have +returned, at which point the program crashes. Panics can be initiated by +invoking panic directly. They can also be caused by runtime errors, such as +out-of-bounds array accesses. +

+ +

+Recover is a built-in function that regains control of a panicking +goroutine. Recover is only useful inside deferred functions. During normal +execution, a call to recover will return nil and have no other effect. If the +current goroutine is panicking, a call to recover will capture the value given +to panic and resume normal execution. +

+ +

+Here's an example program that demonstrates the mechanics of panic and defer: +

+ +
package main
+
+import "fmt"
+
+func main() {
+    f()
+    fmt.Println("Returned normally from f.")
+}
+
+func f() {
+    defer func() {
+        if r := recover(); r != nil {
+            fmt.Println("Recovered in f", r)
+        }
+    }()
+    fmt.Println("Calling g.")
+    g(0)
+    fmt.Println("Returned normally from g.")
+}
+
+func g(i int) {
+    if i > 3 {
+        fmt.Println("Panicking!")
+        panic(fmt.Sprintf("%v", i))
+    }
+    defer fmt.Println("Defer in g", i)
+    fmt.Println("Printing in g", i)
+    g(i + 1)
+}
+
+ +

+The function g takes the int i, and panics if i is greater than 3, or else it +calls itself with the argument i+1. The function f defers a function that calls +recover and prints the recovered value (if it is non-nil). Try to picture what +the output of this program might be before reading on. +

+ +

+The program will output: +

+ +
Calling g.
+Printing in g 0
+Printing in g 1
+Printing in g 2
+Printing in g 3
+Panicking!
+Defer in g 3
+Defer in g 2
+Defer in g 1
+Defer in g 0
+Recovered in f 4
+Returned normally from f.
+ +

+If we remove the deferred function from f the panic is not recovered and +reaches the top of the goroutine's call stack, terminating the program. This +modified program will output: +

+ +
Calling g.
+Printing in g 0
+Printing in g 1
+Printing in g 2
+Printing in g 3
+Panicking!
+Defer in g 3
+Defer in g 2
+Defer in g 1
+Defer in g 0
+panic: 4
+ 
+panic PC=0x2a9cd8
+[stack trace omitted]
+ +

+For a real-world example of panic and recover, see the +json package from the Go standard library. +It decodes JSON-encoded data with a set of recursive functions. +When malformed JSON is encountered, the parser calls panic is to unwind the +stack to the top-level function call, which recovers from the panic and returns +an appropriate error value (see the 'error' and 'unmarshal' functions in +decode.go). +

+ +

+The convention in the Go libraries is that even when a package uses panic +internally, its external API still presents explicit error return values. +

+ +

+Other uses of defer (beyond the file.Close() example given earlier) +include releasing a mutex: +

+ +
mu.Lock()
+defer mu.Unlock()
+ +

+printing a footer: +

+ +
printHeader()
+defer printFooter()
+ +

+and more. +

+ +

+In summary, the defer statement (with or without panic and recover) provides an +unusual and powerful mechanism for control flow. It can be used to model a +number of features implemented by special-purpose structures in other +programming languages. Try it out. +

diff --git a/doc/articles/defer_panic_recover.tmpl b/doc/articles/defer_panic_recover.tmpl new file mode 100644 index 00000000000..90c2b95c094 --- /dev/null +++ b/doc/articles/defer_panic_recover.tmpl @@ -0,0 +1,193 @@ + + +

+Go has the usual mechanisms for control flow: if, for, switch, goto. It also +has the go statement to run code in a separate goroutine. Here I'd like to +discuss some of the less common ones: defer, panic, and recover. +

+ +

+A defer statement pushes a function call onto a list. The list of saved +calls is executed after the surrounding function returns. Defer is commonly +used to simplify functions that perform various clean-up actions. +

+ +

+For example, let's look at a function that opens two files and copies the +contents of one file to the other: +

+ +{{code "progs/defer.go" `/func CopyFile/` `/STOP/`}} + +

+This works, but there is a bug. If the second call to os.Open fails, the +function will return without closing the source file. This can be easily +remedied by putting a call to src.Close() before the second return statement, +but if the function were more complex the problem might not be so easily +noticed and resolved. By introducing defer statements we can ensure that the +files are always closed: +

+ +{{code "progs/defer2.go" `/func CopyFile/` `/STOP/`}} + +

+Defer statements allow us to think about closing each file right after opening +it, guaranteeing that, regardless of the number of return statements in the +function, the files will be closed. +

+ +

+The behavior of defer statements is straightforward and predictable. There are +three simple rules: +

+ +

+1. A deferred function's arguments are evaluated when the defer statement is +evaluated. +

+ +

+In this example, the expression "i" is evaluated when the Println call is +deferred. The deferred call will print "0" after the function returns. +

+ +{{code "progs/defer.go" `/func a/` `/STOP/`}} + +

+2. Deferred function calls are executed in Last In First Out order +after the surrounding function returns. +

+ +

+This function prints "3210": +

+ +{{code "progs/defer.go" `/func b/` `/STOP/`}} + +

+3. Deferred functions may read and assign to the returning function's named +return values. +

+ +

+In this example, a deferred function increments the return value i after +the surrounding function returns. Thus, this function returns 2: +

+ +{{code "progs/defer.go" `/func c/` `/STOP/`}} + +

+This is convenient for modifying the error return value of a function; we will +see an example of this shortly. +

+ +

+Panic is a built-in function that stops the ordinary flow of control and +begins panicking. When the function F calls panic, execution of F stops, +any deferred functions in F are executed normally, and then F returns to its +caller. To the caller, F then behaves like a call to panic. The process +continues up the stack until all functions in the current goroutine have +returned, at which point the program crashes. Panics can be initiated by +invoking panic directly. They can also be caused by runtime errors, such as +out-of-bounds array accesses. +

+ +

+Recover is a built-in function that regains control of a panicking +goroutine. Recover is only useful inside deferred functions. During normal +execution, a call to recover will return nil and have no other effect. If the +current goroutine is panicking, a call to recover will capture the value given +to panic and resume normal execution. +

+ +

+Here's an example program that demonstrates the mechanics of panic and defer: +

+ +{{code "progs/defer2.go" `/package main/` `/STOP/`}} + +

+The function g takes the int i, and panics if i is greater than 3, or else it +calls itself with the argument i+1. The function f defers a function that calls +recover and prints the recovered value (if it is non-nil). Try to picture what +the output of this program might be before reading on. +

+ +

+The program will output: +

+ +
Calling g.
+Printing in g 0
+Printing in g 1
+Printing in g 2
+Printing in g 3
+Panicking!
+Defer in g 3
+Defer in g 2
+Defer in g 1
+Defer in g 0
+Recovered in f 4
+Returned normally from f.
+ +

+If we remove the deferred function from f the panic is not recovered and +reaches the top of the goroutine's call stack, terminating the program. This +modified program will output: +

+ +
Calling g.
+Printing in g 0
+Printing in g 1
+Printing in g 2
+Printing in g 3
+Panicking!
+Defer in g 3
+Defer in g 2
+Defer in g 1
+Defer in g 0
+panic: 4
+ 
+panic PC=0x2a9cd8
+[stack trace omitted]
+ +

+For a real-world example of panic and recover, see the +json package from the Go standard library. +It decodes JSON-encoded data with a set of recursive functions. +When malformed JSON is encountered, the parser calls panic is to unwind the +stack to the top-level function call, which recovers from the panic and returns +an appropriate error value (see the 'error' and 'unmarshal' functions in +decode.go). +

+ +

+The convention in the Go libraries is that even when a package uses panic +internally, its external API still presents explicit error return values. +

+ +

+Other uses of defer (beyond the file.Close() example given earlier) +include releasing a mutex: +

+ +
mu.Lock()
+defer mu.Unlock()
+ +

+printing a footer: +

+ +
printHeader()
+defer printFooter()
+ +

+and more. +

+ +

+In summary, the defer statement (with or without panic and recover) provides an +unusual and powerful mechanism for control flow. It can be used to model a +number of features implemented by special-purpose structures in other +programming languages. Try it out. +

diff --git a/doc/makehtml b/doc/makehtml index 69e8e2b676b..8a029132f46 100755 --- a/doc/makehtml +++ b/doc/makehtml @@ -5,8 +5,8 @@ set -e -TMPL=${1:-go_tutorial.tmpl} # input file -HTML=$(basename $TMPL .tmpl).html # output file (basename) +TMPL=${1:-go_tutorial.tmpl} # input file +HTML=$(dirname $TMPL)/$(basename $TMPL .tmpl).html # output file if ! test -w $HTML then diff --git a/doc/progs/defer.go b/doc/progs/defer.go new file mode 100644 index 00000000000..f52278aef29 --- /dev/null +++ b/doc/progs/defer.go @@ -0,0 +1,53 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code snippets included in "Defer, Panic, an Recover." + +package main + +import ( + "fmt" + "io" + "os" +) + +func a() { + i := 0 + defer fmt.Println(i) + i++ + return +} +// STOP OMIT + +func b() { + for i := 0; i < 4; i++ { + defer fmt.Print(i) + } +} +// STOP OMIT + +func c() (i int) { + defer func() { i++ }() + return 1 +} +// STOP OMIT + +// Intial version. +func CopyFile(dstName, srcName string) (written int64, err error) { + src, err := os.Open(srcName) + if err != nil { + return + } + + dst, err := os.Create(dstName) + if err != nil { + return + } + + written, err = io.Copy(dst, src) + dst.Close() + src.Close() + return +} +// STOP OMIT diff --git a/doc/progs/defer2.go b/doc/progs/defer2.go new file mode 100644 index 00000000000..be6791d5c7b --- /dev/null +++ b/doc/progs/defer2.go @@ -0,0 +1,56 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code snippets included in "Defer, Panic, an Recover." + +package main + +import "fmt" +import "io" // OMIT +import "os" // OMIT + +func main() { + f() + fmt.Println("Returned normally from f.") +} + +func f() { + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered in f", r) + } + }() + fmt.Println("Calling g.") + g(0) + fmt.Println("Returned normally from g.") +} + +func g(i int) { + if i > 3 { + fmt.Println("Panicking!") + panic(fmt.Sprintf("%v", i)) + } + defer fmt.Println("Defer in g", i) + fmt.Println("Printing in g", i) + g(i + 1) +} +// STOP OMIT + +// Revised version. +func CopyFile(dstName, srcName string) (written int64, err error) { + src, err := os.Open(srcName) + if err != nil { + return + } + defer src.Close() + + dst, err := os.Create(dstName) + if err != nil { + return + } + defer dst.Close() + + return io.Copy(dst, src) +} +// STOP OMIT diff --git a/doc/progs/run b/doc/progs/run index e90e30781e3..dd586399fab 100755 --- a/doc/progs/run +++ b/doc/progs/run @@ -20,25 +20,41 @@ else $GC file.go fi +defer_panic_recover=" + defer.go + defer2.go +" + +effective_go=" + eff_bytesize.go + eff_qr.go + eff_sequence.go +" + +go_tutorial=" + cat.go + cat_rot13.go + echo.go + file.go + helloworld.go + helloworld3.go + print.go + print_string.go + server.go + server1.go + sieve.go + sieve1.go + sort.go + sortmain.go + strings.go + sum.go +" + for i in \ - helloworld.go \ - helloworld3.go \ - echo.go \ - cat.go \ - cat_rot13.go \ - sum.go \ - sort.go \ - sortmain.go \ - print.go \ - print_string.go \ - sieve.go \ - sieve1.go \ - server1.go \ - strings.go \ - eff_bytesize.go\ - eff_qr.go \ - eff_sequence.go\ - go1.go\ + $defer_panic_recover \ + $effective_go \ + $go_tutorial \ + go1.go \ ; do $GC $i done diff --git a/doc/tmpltohtml.go b/doc/tmpltohtml.go index ab8e490bf21..df761fa421a 100644 --- a/doc/tmpltohtml.go +++ b/doc/tmpltohtml.go @@ -31,6 +31,7 @@ import ( "io/ioutil" "log" "os" + "path/filepath" "regexp" "strings" "text/template" @@ -54,8 +55,8 @@ func main() { } // Read and parse the input. - name := flag.Args()[0] - tmpl := template.New(name).Funcs(templateFuncs) + name := flag.Arg(0) + tmpl := template.New(filepath.Base(name)).Funcs(templateFuncs) if _, err := tmpl.ParseFiles(name); err != nil { log.Fatal(err) }