Library routines must often return some sort of error indication to the caller. As mentioned earlier, Go's multivalue return makes it easy to return a detailed error description alongside the normal return value. By convention, errors have type error, a simple built-in interface. html
庫函數一般必須返回一些錯誤提示給調用者 正如以前提到的 Go函數支持多值返回 能夠很容易地返回正常返回值的同時 返回詳細的錯誤提示 錯誤有相應的類型error 它是一個內建的簡單接口 以下: golang
type error interface { Error() string }
A library writer is free to implement this interface with a richer model under the covers, making it possible not only to see the error but also to provide some context. For example, os.Open returns an os.PathError. express
庫的做者能夠實現這個接口 而且添加更多的特性 以提供錯誤信息外的上下文環境 好比 os.Open返回os.PathError錯誤: app
// PathError records an error and the operation and Path記錄了這個錯誤 還有相關的操做 以及路徑 // file path that caused it. 致使產生這個錯誤的路徑 type PathError struct { Op string // "open", "unlink", etc. Path string // The associated file. Err error // Returned by the system call. } func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
PathError's Error generates a string like this: less
PathError的Error會生產一個以下的字符串: ide
open /etc/passwx: no such file or directory
Such an error, which includes the problematic file name, the operation, and the operating system error it triggered, is useful even if printed far from the call that caused it; it is much more informative than the plain "no such file or directory". 函數
這個錯誤包含了產生錯誤的文件名 相應的操做 以及引起的操做系統錯誤 即便調用的層次比較多 仍是有用的 總比「no such file or directory」更有意義吧 oop
When feasible, error strings should identify their origin, such as by having a prefix naming the package that generated the error. For example, in package image, the string representation for a decoding error due to an unknown format is "image: unknown format". ui
若是有可能的話 錯誤提示字符串須要標記它們的出處 能夠把產生錯誤的包的名字做爲前綴 加在錯誤提示字符串前 好比 在image包中 因爲未知格式致使的解碼錯誤 它產生的錯誤提示字符串就是「image: unkown format" this
Callers that care about the precise error details can use a type switch or a type assertion to look for specific errors and extract details. For PathErrors this might include examining the internal Err field for recoverable failures.
須要知道更多錯誤相關信息的調用者 可使用類型switch或者類型斷言來檢查特定的錯誤 解析出更加詳細的錯誤細節 對PathError來講 它可能會檢查內部的Err字段 從而判斷是否該錯誤能夠恢復 或者作一些收尾工做
for try := 0; try < 2; try++ { file, err = os.Create(filename) if err == nil { return } if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC { deleteTempFiles() // Recover some space. 釋放存儲空間 continue } return }
The second if statement here is idiomatic Go. The type assertion err.(*os.PathError) is checked with the "comma ok" idiom (mentioned earlier in the context of examining maps). If the type assertion fails, ok will be false, and e will be nil. If it succeeds, ok will be true, which means the error was of type *os.PathError, and then so is e, which we can examine for more information about the error.
上面代碼中的第二個if語句是Go的使用習慣 使用」comma ok"來檢查類型斷言err.(*os.PathError)的結果 若是類型斷言失敗 ok的值爲false e的值爲nil 若是檢查成功 ok的值爲true 這意味着產生的錯誤是*os.PathError
The usual way to report an error to a caller is to return an error as an extra return value. The canonical Read method is a well-known instance; it returns a byte count and an error. But what if the error is unrecoverable? Sometimes the program simply cannot continue.
報告產生錯誤的一般作法是返回一個錯誤 Read方法就是比較好的例子 它返回讀入的字節數 以及一個錯誤提示 但若是產生的錯誤是不可挽救的 那如何是好呢? 有些時候 產生錯誤會直接致使程序不可繼續執行
For this purpose, there is a built-in function panic that in effect creates a run-time error that will stop the program (but see the next section). The function takes a single argument of arbitrary type—often a string—to be printed as the program dies. It's also a way to indicate that something impossible has happened, such as exiting an infinite loop. In fact, the compiler recognizes a panic at the end of a function and suppresses the usual check for a return statement.
爲了解決這個問題 有個內建函數panic 它會建立運行時錯誤 迫使程序中止運行 panic接受一個任意類型的參數 一般是字符串告訴咱們程序掛了 也能夠用它來告知咱們 某些不可能的事情發生了 好比 從死循環裏退了出來 實際上 編譯器在函數退出前識別panic 而後抑制了正常的返回語句檢查
// A toy implementation of cube root using Newton's method. func CubeRoot(x float64) float64 { z := x/3 // Arbitrary initial value for i := 0; i < 1e6; i++ { prevz := z z -= (z*z*z-x) / (3*z*z) if veryClose(z, prevz) { return z } } // A million iterations has not converged; something is wrong. panic(fmt.Sprintf("CubeRoot(%g) did not converge", x)) }
This is only an example but real library functions should avoid panic. If the problem can be masked or worked around, it's always better to let things continue to run rather than taking down the whole program. One possible counterexample is during initialization: if the library truly cannot set itself up, it might be reasonable to panic, so to speak.
這僅僅是一個例子 可是正真的庫函數應該避免panic 若是產生的問題能夠被忽略 或者是能夠解決掉 那麼繼續執行程序總比掛了好 另外一個反面例子是在初始化過程當中產生的 若是庫自身不能正確地執行 那麼產生panic是合情合理的:
var user = os.Getenv("USER") func init() { if user == "" { panic("no value for $USER") } }
When panic is called, including implicitly for run-time errors such as indexing a slice out of bounds or failing a type assertion, it immediately stops execution of the current function and begins unwinding the stack of the goroutine, running any deferred functions along the way. If that unwinding reaches the top of the goroutine's stack, the program dies. However, it is possible to use the built-in function recover to regain control of the goroutine and resume normal execution.
當panic被調用後 它會馬上終止當前的程序執行 而且開始退出goroutine的棧 而且執行依次執行被defer的函數 若是這個過程到了最後一個goroutine的執行棧 程序也就掛了 可是 使用內建函數recover是能夠處理panic 而且讓程序繼續執行
A call to recover stops the unwinding and returns the argument passed to panic. Because the only code that runs while unwinding is inside deferred functions, recover is only useful inside deferred functions.
調用recover會中止退棧 而且返回傳遞給panic的參數 由於panic後只有被defer的函數在跑 recover只在defer函數裏有用
One application of recover is to shut down a failing goroutine inside a server without killing the other executing goroutines.
recover的一個應用場景是 關掉server內的一個產生錯誤的goroutine 可是不去影響到其它的goroutine
func server(workChan <-chan *Work) { for work := range workChan { go safelyDo(work) } } func safelyDo(work *Work) { defer func() { if err := recover(); err != nil { log.Println("work failed:", err) } }() do(work) }
In this example, if do(work) panics, the result will be logged and the goroutine will exit cleanly without disturbing the others. There's no need to do anything else in the deferred closure; calling recover handles the condition completely.
上面這段代碼 若是do(work)產生了panic 那麼panic會被記錄下了 對於的goroutine會退出 可是不會影響其它的goroutine defer函數並不須要作其它的事情 調用recover就ok了
Because recover always returns nil unless called directly from a deferred function, deferred code can call library routines that themselves use panic and recover without failing. As an example, the deferred function in safelyDo might call a logging function before calling recover, and that logging code would run unaffected by the panicking state.
因爲在defer函數外調用recover老是返回nil 被defer的代碼能夠調用那些使用了panic和recover的庫函數 舉例來講 上面代碼中的safelyDo可能會在調用recover以前調用logging函數 那麼logging相關的代碼能夠不受當前panic的影響
With our recovery pattern in place, the do function (and anything it calls) can get out of any bad situation cleanly by calling panic. We can use that idea to simplify error handling in complex software. Let's look at an idealized excerpt from the regexp package, which reports parsing errors by calling panic with a local error type. Here's the definition of Error, an error method, and the Compile function.
知道了如何使用panic和recover後 do函數能夠在遇到噁心的問題使用panic來退出執行 咱們可使用這個想法來簡化複雜軟件的錯誤處理 咱們如今看一下理想化的regexp包中摘出來的代碼片斷 它經過調用panic來報告本身定義的錯誤 下面是Error error方法 和Compile函數的定義:
// Error is the type of a parse error; it satisfies the error interface. type Error string func (e Error) Error() string { return string(e) } // error is a method of *Regexp that reports parsing errors by // panicking with an Error. func (regexp *Regexp) error(err string) { panic(Error(err)) } // Compile returns a parsed representation of the regular expression. func Compile(str string) (regexp *Regexp, err error) { regexp = new(Regexp) // doParse will panic if there is a parse error. defer func() { if e := recover(); e != nil { regexp = nil // Clear return value. err = e.(Error) // Will re-panic if not a parse error. } }() return regexp.doParse(str), nil }
If doParse panics, the recovery block will set the return value to nil—deferred functions can modify named return values. It then will then check, in the assignment to err, that the problem was a parse error by asserting that it has the local type Error. If it does not, the type assertion will fail, causing a run-time error that continues the stack unwinding as though nothing had interrupted it. This check means that if something unexpected happens, such as an index out of bounds, the code will fail even though we are using panic and recover to handle user-triggered errors.
上面代碼中 doParse若是產生了panic 恢復代碼會把返回值設置爲nil defer函數能夠修改命名返回值 而後會在賦值給err的時間檢查若是類型斷言視頻 會致使運行時錯誤 這會繼續棧釋放 這裏的檢查也覺得着 若是發生了意外的狀況 好比 index越界 就算咱們使用panic和recover處理了用戶產生的錯誤 那麼這段代碼仍是會執行失敗
With error handling in place, the error method makes it easy to report parse errors without worrying about unwinding the parse stack by hand.
有了錯誤處理 error方法能夠更加簡單地報告解析錯誤 而不用去擔憂如何去處理解析失敗後 釋放棧空間的問題
Useful though this pattern is, it should be used only within a package. Parse turns its internal panic calls into error values; it does not expose panics to its client. That is a good rule to follow.
雖然這種方式頗有用 但只應該在包內使用 Parse會在panic時 把panic轉換成error值 它並不會讓客戶知道產生了panic 謹記
By the way, this re-panic idiom changes the panic value if an actual error occurs. However, both the original and new failures will be presented in the crash report, so the root cause of the problem will still be visible. Thus this simple re-panic approach is usually sufficient—it's a crash after all—but if you want to display only the original value, you can write a little more code to filter unexpected problems and re-panic with the original error. That's left as an exercise for the reader.
另外 這種再次產生panic的用法 若是真的錯誤發生了 會改變panic的值 好在原先的和新產生的錯誤都會在崩潰報告裏出現 因此產生錯誤的根本緣由仍是能夠找到的 因而 這個簡單的再次產生panic的用法就足夠用了 但若是你只想展現原來的錯誤值 那麼你能夠寫代碼 過濾掉非指望中的錯誤 而後用原先的錯誤再次產生panic