diff --git a/doc/Makefile b/doc/Makefile index 4e8ba08c17..f65e538d97 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 0000000000..06f7685d48 --- /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 0000000000..90c2b95c09 --- /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 69e8e2b676..8a029132f4 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 0000000000..f52278aef2 --- /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 0000000000..be6791d5c7 --- /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 e90e30781e..dd586399fa 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 ab8e490bf2..df761fa421 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) }