diff --git a/doc/Makefile b/doc/Makefile index f65e538d97..daa0a5ea2b 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -8,7 +8,14 @@ TARG=tmpltohtml GOFILES=\ tmpltohtml.go\ -all: tmpltohtml go_tutorial.html effective_go.html go1.html articles/defer_panic_recover.html +HTML=\ + articles/defer_panic_recover.html\ + articles/error_handling.html\ + effective_go.html\ + go1.html\ + go_tutorial.html\ + +all: tmpltohtml $(HTML) %.html: %.tmpl tmpltohtml ./makehtml $*.tmpl diff --git a/doc/articles/error_handling.html b/doc/articles/error_handling.html new file mode 100644 index 0000000000..1a69324107 --- /dev/null +++ b/doc/articles/error_handling.html @@ -0,0 +1,447 @@ + + +

+If you have written any Go code you have probably encountered the built-in +error type. Go code uses error values to +indicate an abnormal state. For example, the os.Open function +returns a non-nil error value when it fails to open a file. +

+ +
func Open(name string) (file *File, err error)
+
+ +

+The following code uses os.Open to open a file. If an error +occurs it calls log.Fatal to print the error message and stop. +

+ +
    f, err := os.Open("filename.ext")
+    if err != nil {
+        log.Fatal(err)
+    }
+    // do something with the open *File f
+
+ +

+You can get a lot done in Go knowing just this about the error +type, but in this article we'll take a closer look at error and +discuss some good practices for error handling in Go. +

+ +

+The error type +

+ +

+The error type is an interface type. An error +variable represents any value that can describe itself as a string. Here is the +interface's declaration: +

+ +
type error interface {
+    Error() string
+}
+ +

+The error type, as with all built in types, is +predeclared in the +universe block. +

+ +

+The most commonly-used error implementation is the +errors package's unexported errorString type. +

+ +
// errorString is a trivial implementation of error.
+type errorString struct {
+    s string
+}
+
+func (e *errorString) Error() string {
+    return e.s
+}
+
+ +

+You can construct one of these values with the errors.New +function. It takes a string that it converts to an errors.errorString +and returns as an error value. +

+ +
// New returns an error that formats as the given text.
+func New(text string) error {
+    return &errorString{text}
+}
+
+ +

+Here's how you might use errors.New: +

+ +
func Sqrt(f float64) (float64, error) {
+    if f < 0 {
+        return 0, errors.New("math: square root of negative number")
+    }
+    // implementation
+}
+
+ +

+A caller passing a negative argument to Sqrt receives a non-nil +error value (whose concrete representation is an +errors.errorString value). The caller can access the error string +("math: square root of...") by calling the error's +Error method, or by just printing it: +

+ +
    f, err := Sqrt(-1)
+    if err != nil {
+        fmt.Println(err)
+    }
+
+ +

+The fmt package formats an error value +by calling its Error() string method. +

+ +

+It is the error implementation's responsibility to summarize the context. +The error returned by os.Open formats as "open /etc/passwd: +permission denied," not just "permission denied." The error returned by our +Sqrt is missing information about the invalid argument. +

+ +

+To add that information, a useful function is the fmt package's +Errorf. It formats a string according to Printf's +rules and returns it as an error created by +errors.New. +

+ +
    if f < 0 {
+        return 0, fmt.Errorf("math: square root of negative number %g", f)
+    }
+
+ +

+In many cases fmt.Errorf is good enough, but since +error is an interface, you can use arbitrary data structures as +error values, to allow callers to inspect the details of the error. +

+ +

+For instance, our hypothetical callers might want to recover the invalid +argument passed to Sqrt. We can enable that by defining a new +error implementation instead of using errors.errorString: +

+ +
type NegativeSqrtError float64
+
+func (f NegativeSqrtError) Error() string {
+    return fmt.Sprintf("math: square root of negative number %g", float64(f))
+}
+
+ +

+A sophisticated caller can then use a +type assertion to check for a +NegativeSqrtError and handle it specially, while callers that just +pass the error to fmt.Println or log.Fatal will see +no change in behavior. +

+ +

+As another example, the json package specifies a +SyntaxError type that the json.Decode function +returns when it encounters a syntax error parsing a JSON blob. +

+ +
type SyntaxError struct {
+    msg    string // description of error
+    Offset int64  // error occurred after reading Offset bytes
+}
+
+func (e *SyntaxError) Error() string { return e.msg }
+
+ +

+The Offset field isn't even shown in the default formatting of the +error, but callers can use it to add file and line information to their error +messages: +

+ +
    if err := dec.Decode(&val); err != nil {
+        if serr, ok := err.(*json.SyntaxError); ok {
+            line, col := findLine(f, serr.Offset)
+            return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
+        }
+        return err
+    }
+
+ +

+(This is a slightly simplified version of some +actual code +from the Camlistore project.) +

+ +

+The error interface requires only a Error method; +specific error implementations might have additional methods. For instance, the +net package returns errors of type +error, following the usual convention, but some of the error +implementations have additional methods defined by the net.Error +interface: +

+ +
package net
+
+type Error interface {
+    error
+    Timeout() bool   // Is the error a timeout?
+    Temporary() bool // Is the error temporary?
+}
+ +

+Client code can test for a net.Error with a type assertion and +then distinguish transient network errors from permanent ones. For instance, a +web crawler might sleep and retry when it encounters a temporary error and give +up otherwise. +

+ +
        if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
+            time.Sleep(1e9)
+            continue
+        }
+        if err != nil {
+            log.Fatal(err)
+        }
+
+ +

+Simplifying repetitive error handling +

+ +

+In Go, error handling is important. The language's design and conventions +encourage you to explicitly check for errors where they occur (as distinct from +the convention in other languages of throwing exceptions and sometimes catching +them). In some cases this makes Go code verbose, but fortunately there are some +techniques you can use to minimize repetitive error handling. +

+ +

+Consider an App Engine +application with an HTTP handler that retrieves a record from the datastore and +formats it with a template. +

+ +
func init() {
+    http.HandleFunc("/view", viewRecord)
+}
+
+func viewRecord(w http.ResponseWriter, r *http.Request) {
+    c := appengine.NewContext(r)
+    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
+    record := new(Record)
+    if err := datastore.Get(c, key, record); err != nil {
+        http.Error(w, err.Error(), 500)
+        return
+    }
+    if err := viewTemplate.Execute(w, record); err != nil {
+        http.Error(w, err.Error(), 500)
+    }
+}
+
+ +

+This function handles errors returned by the datastore.Get +function and viewTemplate's Execute method. In both +cases, it presents a simple error message to the user with the HTTP status code +500 ("Internal Server Error"). This looks like a manageable amount of code, but +add some more HTTP handlers and you quickly end up with many copies of +identical error handling code. +

+ +

+To reduce the repetition we can define our own HTTP appHandler +type that includes an error return value: +

+ +
type appHandler func(http.ResponseWriter, *http.Request) error
+
+ +

+Then we can change our viewRecord function to return errors: +

+ +
func viewRecord(w http.ResponseWriter, r *http.Request) error {
+    c := appengine.NewContext(r)
+    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
+    record := new(Record)
+    if err := datastore.Get(c, key, record); err != nil {
+        return err
+    }
+    return viewTemplate.Execute(w, record)
+}
+
+ +

+This is simpler than the original version, but the http package doesn't understand functions that return +error. +To fix this we can implement the http.Handler interface's +ServeHTTP method on appHandler: +

+ +
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+    if err := fn(w, r); err != nil {
+        http.Error(w, err.Error(), 500)
+    }
+}
+
+ +

+The ServeHTTP method calls the appHandler function +and displays the returned error (if any) to the user. Notice that the method's +receiver, fn, is a function. (Go can do that!) The method invokes +the function by calling the receiver in the expression fn(w, r). +

+ +

+Now when registering viewRecord with the http package we use the +Handle function (instead of HandleFunc) as +appHandler is an http.Handler (not an +http.HandlerFunc). +

+ +
func init() {
+    http.Handle("/view", appHandler(viewRecord))
+}
+
+ +

+With this basic error handling infrastructure in place, we can make it more +user friendly. Rather than just displaying the error string, it would be better +to give the user a simple error message with an appropriate HTTP status code, +while logging the full error to the App Engine developer console for debugging +purposes. +

+ +

+To do this we create an appError struct containing an +error and some other fields: +

+ +
type appError struct {
+    Error   error
+    Message string
+    Code    int
+}
+
+ +

+Next we modify the appHandler type to return *appError values: +

+ +
type appHandler func(http.ResponseWriter, *http.Request) *appError
+
+ +

+(It's usually a mistake to pass back the concrete type of an error rather than +error, for reasons to be discussed in another article, but +it's the right thing to do here because ServeHTTP is the only +place that sees the value and uses its contents.) +

+ +

+And make appHandler's ServeHTTP method display the +appError's Message to the user with the correct HTTP +status Code and log the full Error to the developer +console: +

+ +
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+    if e := fn(w, r); e != nil { // e is *appError, not os.Error.
+        c := appengine.NewContext(r)
+        c.Errorf("%v", e.Error)
+        http.Error(w, e.Message, e.Code)
+    }
+}
+
+ +

+Finally, we update viewRecord to the new function signature and +have it return more context when it encounters an error: +

+ +
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
+    c := appengine.NewContext(r)
+    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
+    record := new(Record)
+    if err := datastore.Get(c, key, record); err != nil {
+        return &appError{err, "Record not found", 404}
+    }
+    if err := viewTemplate.Execute(w, record); err != nil {
+        return &appError{err, "Can't display record", 500}
+    }
+    return nil
+}
+
+ +

+This version of viewRecord is the same length as the original, but +now each of those lines has specific meaning and we are providing a friendlier +user experience. +

+ +

+It doesn't end there; we can further improve the error handling in our +application. Some ideas: +

+ + + +

+Conclusion +

+ +

+Proper error handling is an essential requirement of good software. By +employing the techniques described in this post you should be able to write +more reliable and succinct Go code. +

diff --git a/doc/articles/error_handling.tmpl b/doc/articles/error_handling.tmpl new file mode 100644 index 0000000000..75800ae21a --- /dev/null +++ b/doc/articles/error_handling.tmpl @@ -0,0 +1,312 @@ + + +

+If you have written any Go code you have probably encountered the built-in +error type. Go code uses error values to +indicate an abnormal state. For example, the os.Open function +returns a non-nil error value when it fails to open a file. +

+ +{{code "progs/error.go" `/func Open/`}} + +

+The following code uses os.Open to open a file. If an error +occurs it calls log.Fatal to print the error message and stop. +

+ +{{code "progs/error.go" `/func openFile/` `/STOP/`}} + +

+You can get a lot done in Go knowing just this about the error +type, but in this article we'll take a closer look at error and +discuss some good practices for error handling in Go. +

+ +

+The error type +

+ +

+The error type is an interface type. An error +variable represents any value that can describe itself as a string. Here is the +interface's declaration: +

+ +
type error interface {
+    Error() string
+}
+ +

+The error type, as with all built in types, is +predeclared in the +universe block. +

+ +

+The most commonly-used error implementation is the +errors package's unexported errorString type. +

+ +{{code "progs/error.go" `/errorString/` `/STOP/`}} + +

+You can construct one of these values with the errors.New +function. It takes a string that it converts to an errors.errorString +and returns as an error value. +

+ +{{code "progs/error.go" `/New/` `/STOP/`}} + +

+Here's how you might use errors.New: +

+ +{{code "progs/error.go" `/func Sqrt/` `/STOP/`}} + +

+A caller passing a negative argument to Sqrt receives a non-nil +error value (whose concrete representation is an +errors.errorString value). The caller can access the error string +("math: square root of...") by calling the error's +Error method, or by just printing it: +

+ +{{code "progs/error.go" `/func printErr/` `/STOP/`}} + +

+The fmt package formats an error value +by calling its Error() string method. +

+ +

+It is the error implementation's responsibility to summarize the context. +The error returned by os.Open formats as "open /etc/passwd: +permission denied," not just "permission denied." The error returned by our +Sqrt is missing information about the invalid argument. +

+ +

+To add that information, a useful function is the fmt package's +Errorf. It formats a string according to Printf's +rules and returns it as an error created by +errors.New. +

+ +{{code "progs/error.go" `/fmtError/` `/STOP/`}} + +

+In many cases fmt.Errorf is good enough, but since +error is an interface, you can use arbitrary data structures as +error values, to allow callers to inspect the details of the error. +

+ +

+For instance, our hypothetical callers might want to recover the invalid +argument passed to Sqrt. We can enable that by defining a new +error implementation instead of using errors.errorString: +

+ +{{code "progs/error.go" `/type NegativeSqrtError/` `/STOP/`}} + +

+A sophisticated caller can then use a +type assertion to check for a +NegativeSqrtError and handle it specially, while callers that just +pass the error to fmt.Println or log.Fatal will see +no change in behavior. +

+ +

+As another example, the json package specifies a +SyntaxError type that the json.Decode function +returns when it encounters a syntax error parsing a JSON blob. +

+ +{{code "progs/error.go" `/type SyntaxError/` `/STOP/`}} + +

+The Offset field isn't even shown in the default formatting of the +error, but callers can use it to add file and line information to their error +messages: +

+ +{{code "progs/error.go" `/func decodeError/` `/STOP/`}} + +

+(This is a slightly simplified version of some +actual code +from the Camlistore project.) +

+ +

+The error interface requires only a Error method; +specific error implementations might have additional methods. For instance, the +net package returns errors of type +error, following the usual convention, but some of the error +implementations have additional methods defined by the net.Error +interface: +

+ +
package net
+
+type Error interface {
+    error
+    Timeout() bool   // Is the error a timeout?
+    Temporary() bool // Is the error temporary?
+}
+ +

+Client code can test for a net.Error with a type assertion and +then distinguish transient network errors from permanent ones. For instance, a +web crawler might sleep and retry when it encounters a temporary error and give +up otherwise. +

+ +{{code "progs/error.go" `/func netError/` `/STOP/`}} + +

+Simplifying repetitive error handling +

+ +

+In Go, error handling is important. The language's design and conventions +encourage you to explicitly check for errors where they occur (as distinct from +the convention in other languages of throwing exceptions and sometimes catching +them). In some cases this makes Go code verbose, but fortunately there are some +techniques you can use to minimize repetitive error handling. +

+ +

+Consider an App Engine +application with an HTTP handler that retrieves a record from the datastore and +formats it with a template. +

+ +{{code "progs/error2.go" `/func init/` `/STOP/`}} + +

+This function handles errors returned by the datastore.Get +function and viewTemplate's Execute method. In both +cases, it presents a simple error message to the user with the HTTP status code +500 ("Internal Server Error"). This looks like a manageable amount of code, but +add some more HTTP handlers and you quickly end up with many copies of +identical error handling code. +

+ +

+To reduce the repetition we can define our own HTTP appHandler +type that includes an error return value: +

+ +{{code "progs/error3.go" `/type appHandler/`}} + +

+Then we can change our viewRecord function to return errors: +

+ +{{code "progs/error3.go" `/func viewRecord/` `/STOP/`}} + +

+This is simpler than the original version, but the http package doesn't understand functions that return +error. +To fix this we can implement the http.Handler interface's +ServeHTTP method on appHandler: +

+ +{{code "progs/error3.go" `/ServeHTTP/` `/STOP/`}} + +

+The ServeHTTP method calls the appHandler function +and displays the returned error (if any) to the user. Notice that the method's +receiver, fn, is a function. (Go can do that!) The method invokes +the function by calling the receiver in the expression fn(w, r). +

+ +

+Now when registering viewRecord with the http package we use the +Handle function (instead of HandleFunc) as +appHandler is an http.Handler (not an +http.HandlerFunc). +

+ +{{code "progs/error3.go" `/func init/` `/STOP/`}} + +

+With this basic error handling infrastructure in place, we can make it more +user friendly. Rather than just displaying the error string, it would be better +to give the user a simple error message with an appropriate HTTP status code, +while logging the full error to the App Engine developer console for debugging +purposes. +

+ +

+To do this we create an appError struct containing an +error and some other fields: +

+ +{{code "progs/error4.go" `/type appError/` `/STOP/`}} + +

+Next we modify the appHandler type to return *appError values: +

+ +{{code "progs/error4.go" `/type appHandler/`}} + +

+(It's usually a mistake to pass back the concrete type of an error rather than +error, for reasons to be discussed in another article, but +it's the right thing to do here because ServeHTTP is the only +place that sees the value and uses its contents.) +

+ +

+And make appHandler's ServeHTTP method display the +appError's Message to the user with the correct HTTP +status Code and log the full Error to the developer +console: +

+ +{{code "progs/error4.go" `/ServeHTTP/` `/STOP/`}} + +

+Finally, we update viewRecord to the new function signature and +have it return more context when it encounters an error: +

+ +{{code "progs/error4.go" `/func viewRecord/` `/STOP/`}} + +

+This version of viewRecord is the same length as the original, but +now each of those lines has specific meaning and we are providing a friendlier +user experience. +

+ +

+It doesn't end there; we can further improve the error handling in our +application. Some ideas: +

+ + + +

+Conclusion +

+ +

+Proper error handling is an essential requirement of good software. By +employing the techniques described in this post you should be able to write +more reliable and succinct Go code. +

diff --git a/doc/progs/error.go b/doc/progs/error.go new file mode 100644 index 0000000000..3f98709f7c --- /dev/null +++ b/doc/progs/error.go @@ -0,0 +1,115 @@ +// 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 "Error Handling and Go." + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net" + "os" + "time" +) + +type File struct{} + +func Open(name string) (file *File, err error) + +func openFile() { // OMIT + f, err := os.Open("filename.ext") + if err != nil { + log.Fatal(err) + } + // do something with the open *File f + // STOP OMIT + _ = f +} + +// errorString is a trivial implementation of error. +type errorString struct { + s string +} + +func (e *errorString) Error() string { + return e.s +} +// STOP OMIT + +// New returns an error that formats as the given text. +func New(text string) error { + return &errorString{text} +} +// STOP OMIT + +func Sqrt(f float64) (float64, error) { + if f < 0 { + return 0, errors.New("math: square root of negative number") + } + // implementation + return 0, nil // OMIT +} +// STOP OMIT + +func printErr() (int, error) { // OMIT + f, err := Sqrt(-1) + if err != nil { + fmt.Println(err) + } + // STOP OMIT + // fmtError OMIT + if f < 0 { + return 0, fmt.Errorf("math: square root of negative number %g", f) + } + // STOP OMIT + return 0, nil +} + +type NegativeSqrtError float64 + +func (f NegativeSqrtError) Error() string { + return fmt.Sprintf("math: square root of negative number %g", float64(f)) +} +// STOP OMIT + +type SyntaxError struct { + msg string // description of error + Offset int64 // error occurred after reading Offset bytes +} + +func (e *SyntaxError) Error() string { return e.msg } +// STOP OMIT + +func decodeError(dec *json.Decoder, val struct{}) error { // OMIT + var f os.FileInfo // OMIT + if err := dec.Decode(&val); err != nil { + if serr, ok := err.(*json.SyntaxError); ok { + line, col := findLine(f, serr.Offset) + return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err) + } + return err + } + // STOP OMIT + return nil +} + +func findLine(os.FileInfo, int64) (int, int) + +func netError(err error) { // OMIT + for { // OMIT + if nerr, ok := err.(net.Error); ok && nerr.Temporary() { + time.Sleep(1e9) + continue + } + if err != nil { + log.Fatal(err) + } + // STOP OMIT + } +} + +func main() {} diff --git a/doc/progs/error2.go b/doc/progs/error2.go new file mode 100644 index 0000000000..fe72350181 --- /dev/null +++ b/doc/progs/error2.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 "Error Handling and Go." + +package main + +import ( + "net/http" + "text/template" +) + +func init() { + http.HandleFunc("/view", viewRecord) +} + +func viewRecord(w http.ResponseWriter, r *http.Request) { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + http.Error(w, err.Error(), 500) + return + } + if err := viewTemplate.Execute(w, record); err != nil { + http.Error(w, err.Error(), 500) + } +} +// STOP OMIT + +type ap struct{} + +func (ap) NewContext(*http.Request) *ctx { return nil } + +type ctx struct{} + +func (*ctx) Errorf(string, ...interface{}) {} + +var appengine ap + +type ds struct{} + +func (ds) NewKey(*ctx, string, string, int, *int) string { return "" } +func (ds) Get(*ctx, string, *Record) error { return nil } + +var datastore ds + +type Record struct{} + +var viewTemplate *template.Template + +func main() {} diff --git a/doc/progs/error3.go b/doc/progs/error3.go new file mode 100644 index 0000000000..8305edc420 --- /dev/null +++ b/doc/progs/error3.go @@ -0,0 +1,60 @@ +// 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 "Error Handling and Go." + +package main + +import ( + "net/http" + "text/template" +) + +func init() { + http.Handle("/view", appHandler(viewRecord)) +} +// STOP OMIT + +func viewRecord(w http.ResponseWriter, r *http.Request) error { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + return err + } + return viewTemplate.Execute(w, record) +} +// STOP OMIT + +type appHandler func(http.ResponseWriter, *http.Request) error + +func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + http.Error(w, err.Error(), 500) + } +} +// STOP OMIT + +type ap struct{} + +func (ap) NewContext(*http.Request) *ctx { return nil } + +type ctx struct{} + +func (*ctx) Errorf(string, ...interface{}) {} + +var appengine ap + +type ds struct{} + +func (ds) NewKey(*ctx, string, string, int, *int) string { return "" } +func (ds) Get(*ctx, string, *Record) error { return nil } + +var datastore ds + +type Record struct{} + +var viewTemplate *template.Template + +func main() {} diff --git a/doc/progs/error4.go b/doc/progs/error4.go new file mode 100644 index 0000000000..661dcdc2b6 --- /dev/null +++ b/doc/progs/error4.go @@ -0,0 +1,71 @@ +// 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 "Error Handling and Go." + +package main + +import ( + "net/http" + "text/template" +) + +type appError struct { + Error error + Message string + Code int +} +// STOP OMIT + +type appHandler func(http.ResponseWriter, *http.Request) *appError + +func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if e := fn(w, r); e != nil { // e is *appError, not os.Error. + c := appengine.NewContext(r) + c.Errorf("%v", e.Error) + http.Error(w, e.Message, e.Code) + } +} +// STOP OMIT + +func viewRecord(w http.ResponseWriter, r *http.Request) *appError { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + return &appError{err, "Record not found", 404} + } + if err := viewTemplate.Execute(w, record); err != nil { + return &appError{err, "Can't display record", 500} + } + return nil +} +// STOP OMIT + +func init() { + http.Handle("/view", appHandler(viewRecord)) +} + +type ap struct{} + +func (ap) NewContext(*http.Request) *ctx { return nil } + +type ctx struct{} + +func (*ctx) Errorf(string, ...interface{}) {} + +var appengine ap + +type ds struct{} + +func (ds) NewKey(*ctx, string, string, int, *int) string { return "" } +func (ds) Get(*ctx, string, *Record) error { return nil } + +var datastore ds + +type Record struct{} + +var viewTemplate *template.Template + +func main() {} diff --git a/doc/progs/run b/doc/progs/run index d8efe96c25..b4c63f8b3e 100755 --- a/doc/progs/run +++ b/doc/progs/run @@ -31,6 +31,13 @@ effective_go=" eff_sequence.go " +error_handling=" + error.go + error2.go + error3.go + error4.go +" + go_tutorial=" cat.go cat_rot13.go @@ -52,6 +59,7 @@ go_tutorial=" for i in \ $defer_panic_recover \ $effective_go \ + $error_handling \ $go_tutorial \ go1.go \ ; do