]> Cypherpunks repositories - gostls13.git/commitdiff
doc: add Defer, Panic, and Recover article
authorAndrew Gerrand <adg@golang.org>
Mon, 12 Dec 2011 02:15:29 +0000 (13:15 +1100)
committerAndrew Gerrand <adg@golang.org>
Mon, 12 Dec 2011 02:15:29 +0000 (13:15 +1100)
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
doc/articles/defer_panic_recover.html [new file with mode: 0644]
doc/articles/defer_panic_recover.tmpl [new file with mode: 0644]
doc/makehtml
doc/progs/defer.go [new file with mode: 0644]
doc/progs/defer2.go [new file with mode: 0644]
doc/progs/run
doc/tmpltohtml.go

index 4e8ba08c175754615bc225da0e75329e4dee49a2..f65e538d974be849e6945e4b55daa4a5114c6f01 100644 (file)
@@ -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 (file)
index 0000000..06f7685
--- /dev/null
@@ -0,0 +1,274 @@
+<!-- Defer, Panic, and Recover -->
+
+<p>
+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.
+</p>
+<p>
+A <b>defer statement</b> 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.
+</p>
+<p>
+For example, let's look at a function that opens two files and copies the
+contents of one file to the other:
+</p>
+<pre><!--{{code "progs/defer.go" `/func CopyFile/` `/STOP/`}}
+-->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
+}
+</pre>
+
+<p>
+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:
+</p>
+<pre><!--{{code "progs/defer2.go" `/func CopyFile/` `/STOP/`}}
+-->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)
+}
+</pre>
+
+<p>
+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 <i>will</i> be closed.
+</p>
+<p>
+The behavior of defer statements is straightforward and predictable. There are
+three simple rules:
+</p>
+<p>
+1. <i>A deferred function's arguments are evaluated when the defer statement is
+evaluated.</i> 
+</p>
+<p>
+In this example, the expression "i" is evaluated when the Println call is
+deferred. The deferred call will print "0" after the function returns.
+</p>
+<pre><!--{{code "progs/defer.go" `/func a/` `/STOP/`}}
+-->func a() {
+    i := 0
+    defer fmt.Println(i)
+    i++
+    return
+}
+</pre>
+
+<p>
+2. <i>Deferred function calls are executed in Last In First Out order
+</i>after<i> the surrounding function returns.</i> 
+</p>
+<p>
+This function prints "3210":
+</p>
+
+<pre><!--{{code "progs/defer.go" `/func b/` `/STOP/`}}
+-->func b() {
+    for i := 0; i &lt; 4; i++ {
+        defer fmt.Print(i)
+    }
+}
+</pre>
+<p>
+3. <i>Deferred functions may read and assign to the returning function's named
+return values.</i> 
+</p>
+<p>
+In this example, a deferred function increments the return value i <i>after</i>
+the surrounding function returns. Thus, this function returns 2:
+</p>
+
+<pre><!--{{code "progs/defer.go" `/func c/` `/STOP/`}}
+-->func c() (i int) {
+    defer func() { i++ }()
+    return 1
+}
+</pre>
+<p>
+This is convenient for modifying the error return value of a function; we will
+see an example of this shortly.
+</p>
+<p>
+<b>Panic</b> is a built-in function that stops the ordinary flow of control and
+begins <i>panicking</i>. 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.
+</p>
+<p>
+<b>Recover</b> 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.
+</p>
+<p>
+Here's an example program that demonstrates the mechanics of panic and defer:
+</p>
+
+<pre><!--{{code "progs/defer2.go" `/package main/` `/STOP/`}}
+-->package main
+
+import &#34;fmt&#34;
+
+func main() {
+    f()
+    fmt.Println(&#34;Returned normally from f.&#34;)
+}
+
+func f() {
+    defer func() {
+        if r := recover(); r != nil {
+            fmt.Println(&#34;Recovered in f&#34;, r)
+        }
+    }()
+    fmt.Println(&#34;Calling g.&#34;)
+    g(0)
+    fmt.Println(&#34;Returned normally from g.&#34;)
+}
+
+func g(i int) {
+    if i &gt; 3 {
+        fmt.Println(&#34;Panicking!&#34;)
+        panic(fmt.Sprintf(&#34;%v&#34;, i))
+    }
+    defer fmt.Println(&#34;Defer in g&#34;, i)
+    fmt.Println(&#34;Printing in g&#34;, i)
+    g(i + 1)
+}
+</pre>
+<p>
+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.
+</p>
+<p>
+The program will output:
+</p>
+<pre>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.</pre> 
+
+<p>
+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:
+</p>
+<pre>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]</pre> 
+
+<p>
+For a real-world example of <b>panic</b> and <b>recover</b>, see the
+<a href="/pkg/encoding/json/">json package</a> 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
+<a href="/src/pkg/encoding/json/decode.go">decode.go</a>).
+</p>
+
+<p>
+The convention in the Go libraries is that even when a package uses panic
+internally, its external API still presents explicit error return values.
+</p>
+<p>
+Other uses of <b>defer</b> (beyond the file.Close() example given earlier)
+include releasing a mutex:
+</p>
+
+<pre>mu.Lock()
+defer mu.Unlock()</pre> 
+
+<p>
+printing a footer:
+</p>
+<pre>printHeader()
+defer printFooter()</pre> 
+
+<p>
+and more.
+</p>
+<p>
+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.
+</p>
diff --git a/doc/articles/defer_panic_recover.tmpl b/doc/articles/defer_panic_recover.tmpl
new file mode 100644 (file)
index 0000000..90c2b95
--- /dev/null
@@ -0,0 +1,193 @@
+<!-- Defer, Panic, and Recover -->
+
+<p>
+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.
+</p>
+<p>
+A <b>defer statement</b> 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.
+</p>
+<p>
+For example, let's look at a function that opens two files and copies the
+contents of one file to the other:
+</p>
+{{code "progs/defer.go" `/func CopyFile/` `/STOP/`}}
+
+<p>
+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:
+</p>
+{{code "progs/defer2.go" `/func CopyFile/` `/STOP/`}}
+
+<p>
+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 <i>will</i> be closed.
+</p>
+<p>
+The behavior of defer statements is straightforward and predictable. There are
+three simple rules:
+</p>
+<p>
+1. <i>A deferred function's arguments are evaluated when the defer statement is
+evaluated.</i> 
+</p>
+<p>
+In this example, the expression "i" is evaluated when the Println call is
+deferred. The deferred call will print "0" after the function returns.
+</p>
+{{code "progs/defer.go" `/func a/` `/STOP/`}}
+
+<p>
+2. <i>Deferred function calls are executed in Last In First Out order
+</i>after<i> the surrounding function returns.</i> 
+</p>
+<p>
+This function prints "3210":
+</p>
+
+{{code "progs/defer.go" `/func b/` `/STOP/`}}
+<p>
+3. <i>Deferred functions may read and assign to the returning function's named
+return values.</i> 
+</p>
+<p>
+In this example, a deferred function increments the return value i <i>after</i>
+the surrounding function returns. Thus, this function returns 2:
+</p>
+
+{{code "progs/defer.go" `/func c/` `/STOP/`}}
+<p>
+This is convenient for modifying the error return value of a function; we will
+see an example of this shortly.
+</p>
+<p>
+<b>Panic</b> is a built-in function that stops the ordinary flow of control and
+begins <i>panicking</i>. 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.
+</p>
+<p>
+<b>Recover</b> 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.
+</p>
+<p>
+Here's an example program that demonstrates the mechanics of panic and defer:
+</p>
+
+{{code "progs/defer2.go" `/package main/` `/STOP/`}}
+<p>
+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.
+</p>
+<p>
+The program will output:
+</p>
+<pre>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.</pre> 
+
+<p>
+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:
+</p>
+<pre>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]</pre> 
+
+<p>
+For a real-world example of <b>panic</b> and <b>recover</b>, see the
+<a href="/pkg/encoding/json/">json package</a> 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
+<a href="/src/pkg/encoding/json/decode.go">decode.go</a>).
+</p>
+
+<p>
+The convention in the Go libraries is that even when a package uses panic
+internally, its external API still presents explicit error return values.
+</p>
+<p>
+Other uses of <b>defer</b> (beyond the file.Close() example given earlier)
+include releasing a mutex:
+</p>
+
+<pre>mu.Lock()
+defer mu.Unlock()</pre> 
+
+<p>
+printing a footer:
+</p>
+<pre>printHeader()
+defer printFooter()</pre> 
+
+<p>
+and more.
+</p>
+<p>
+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.
+</p>
index 69e8e2b676bd0b7bc45fbd9cf7711bebaf39116d..8a029132f465cb7c3eb0f4f76b91041b37e79dbd 100755 (executable)
@@ -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 (file)
index 0000000..f52278a
--- /dev/null
@@ -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 (file)
index 0000000..be6791d
--- /dev/null
@@ -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
index e90e30781e312379ed2604943bda8c0c015737cf..dd586399fabd86c14ad69539a3dad065594168e1 100755 (executable)
@@ -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
index ab8e490bf21024e447f79e28caa0234e8fe7abbf..df761fa421a5cfd4e04e9d5288bc5869842a9b21 100644 (file)
@@ -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)
        }