一、Panic
1. What is Panic?
What is Panic?
The idiomatic way of handling abnormal conditions in a Go program is using errors. Errors are sufficient for most of the abnormal conditions arising in the program.
But there are some situations where the program cannot continue execution after an abnormal condition. In this case, we use panic to prematurely terminate the program. When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates. This concept will be more clear when we write an example program.
It is possible to regain control of a panicking program using recover which we will discuss later in this tutorial.
panic and recover can be considered similar to try-catch-finally idiom in other languages such as Java except that they are rarely used in Go.
什么是Panic?
在Go程序中处理异常情况的惯用方法是使用错误。对于程序中出现的大多数异常情况,错误是足够的。
但是在某些情况下,程序在出现异常情况后无法继续执行。在这种情况下,我们使用panic来提前终止程序。当一个函数遇到紧急情况时,它的执行将被停止,所有延迟的函数将被执行,然后控制权将返回给它的调用者。这个过程一直持续到当前程序的所有函数都返回为止,此时程序打印panic消息,后跟堆栈跟踪,然后终止。当我们编写示例程序时,这个概念将更加清晰。
使用recover可以重新控制错误程序,我们将在本教程后面讨论。
panic和recover可以被认为类似于其他语言(如Java)中的try-catch-finally习语,只是它们在Go中很少使用。
2. What should panic be used?
When Should Panic Be Used?
One important factor is that you should avoid panic and recover and use errors where ever possible. Only in cases where the program just cannot continue execution should panic and recover mechanism be used.
There are two valid use cases for panic.
An unrecoverable error where the program cannot simply continue its execution.
One example is a web server that fails to bind to the required port. In this case, it's reasonable to panic as there is nothing else to do if the port binding itself fails.
A programmer error.
Let's say we have a method that accepts a pointer as a parameter and someone calls this method using a nil argument. In this case, we can panic as it's a programmer error to call a method with nil argument which was expecting a valid pointer.
什么时候应该使用Panic?
一个重要的因素是,您应该避免错误,尽可能恢复和使用错误。只有在程序无法继续执行的情况下,才应该使用panic和recovery机制。
恐慌有两个有效的用例。
1. 程序无法继续执行的不可恢复的错误。
一个例子是web服务器无法绑定到所需的端口。在这种情况下,Panic是合理的,因为如果端口绑定本身失败,就没有别的办法了。
2. 程序员错误。
假设我们有一个方法接受一个指针作为参数有人用nil参数调用这个方法。在这种情况下,我们可以panic,因为这是一个程序员的错误,调用nil参数的方法,期望一个有效的指针。
3. Example
package main
import (
"fmt"
)
func fullName(firstName *string, lastName *string) {
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
// panic: runtime error: last name cannot be nil
// goroutine 1 [running]:
// main.fullName(0xc000062000?, 0xc0000c9f70?)
// D:/Go/oop/Panic.go:12 +0x114
// main.main()
// D:/Go/oop/Panic.go:20 +0x35
4. Defer Calls During a Panic 延迟panic
package main
import (
"fmt"
)
func fullName(firstName *string, lastName *string) {
defer fmt.Println("deferred call in fullName") // 当defer遇见panic会先执行堆栈里面的所有defer再执行panic
if firstName == nil {
panic("runtime error: first name cannot be nil") // 注意这是panic 不是println
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
// deferred call in fullName
// ddeferred call in main
// dpanic: runtime error: last name cannot be nil
// dgoroutine 1 [running]:
// dmain.fullName(0xc00006af28, 0x0)
// d /tmp/sandbox451943841/prog.go:13 +0x23f
// dmain.main()
// d /tmp/sandbox451943841/prog.go:22 +0xc6
5. Recovering from a Panic 关联
package main
import (
"fmt"
)
func recoverFullName() {
if r := recover(); r!= nil { // 当前recover接受panic的错误信息
fmt.Println("recovered from ", r)
}
}
func fullName(firstName *string, lastName *string) {
defer recoverFullName()
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
// recovered from runtime error: last name cannot be nil
// returned normally from main
// deferred call in main
// Example 2
package main
import (
"fmt"
)
func recoverInvalidAccess() {
if r := recover(); r != nil { // recover接收错误信息
fmt.Println("Recovered", r)
}
}
func invalidSliceAccess() {
defer recoverInvalidAccess()
n := []int{5, 7, 4}
fmt.Println(n[4]) // 运行至此 发生错误 执行panic 执行defer
fmt.Println("normally returned from a")
}
func main() {
invalidSliceAccess()
fmt.Println("normally returned from main")
}
// Recovered runtime error: index out of range [4] with length 3
// normally returned from main
6. Getting Stack Trace after Recover 输出堆栈信息
package main
import (
"fmt"
"runtime/debug"
)
func recoverFullName() {
if r := recover(); r != nil { // 接受错误panic信息
fmt.Println("recovered from ", r)
debug.PrintStack() // debug.PrintStack()函数用于打印当前的堆栈跟踪信息
} // 用于在程序出现错误时输出堆栈跟踪,以帮助调试错误。
}
func fullName(firstName *string, lastName *string) {
defer recoverFullName()
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
// recovered from runtime error: last name cannot be nil
// goroutine 1 [running]:
// runtime/debug.Stack(0x37, 0x0, 0x0)
// /usr/local/go-faketime/src/runtime/debug/stack.go:24 +0x9d
// runtime/debug.PrintStack()
// /usr/local/go-faketime/src/runtime/debug/stack.go:16 +0x22
// main.recoverFullName()
// /tmp/sandbox771195810/prog.go:11 +0xb4
// panic(0x4a1b60, 0x4dc300)
// /usr/local/go-faketime/src/runtime/panic.go:969 +0x166
// main.fullName(0xc0000a2f28, 0x0)
// /tmp/sandbox771195810/prog.go:21 +0x1cb
// main.main()
// /tmp/sandbox771195810/prog.go:30 +0xc6
// returned normally from main
// deferred call in main
7. Panic, Recover and Goroutines
package main
import (
"fmt"
)
func recovery() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}
func sum(a int, b int) {
defer recovery()
fmt.Printf("%d + %d = %d\n", a, b, a+b)
done := make(chan bool)
go divide(a, b, done) // 开启一个新的线程
<-done
}
func divide(a int, b int, done chan bool) {
fmt.Printf("%d / %d = %d", a, b, a/b) // 不能除以0 integer divide by zero
done <- true
}
func main() {
sum(5, 0) // 遇见错误不在向下执行
fmt.Println("normally returned from main")
}
// 5 + 0 = 5
// panic: runtime error: integer divide by zero
// goroutine 18 [running]:
// main.divide(0x5, 0x0, 0xc0000a2000)
// /tmp/sandbox877118715/prog.go:22 +0x167
// created by main.sum
// /tmp/sandbox877118715/prog.go:17 +0x1a9
二、First Class Functions
1. What are first class functions?
A language that supports first class functions allows functions to be assigned to variables, passed as arguments to other functions and returned from other functions. Go has support for first class functions.
In this tutorial, we will discuss the syntax and various use cases of first class functions.
一种支持第一类函数的语言允许函数被分配给变量,作为参数传递给其他函数,并从其他函数返回。Go支持一级函数。
在本教程中,我们将讨论第一类函数的语法和各种用例。
2. Anonymous functions 匿名函数
package main
import (
"fmt"
)
func main() {
a := func() {
fmt.Println("hello world first class function")
}
a()
fmt.Printf("%T", a)
func() {
fmt.Println("hello world first class function")
}()
func(n string) {
fmt.Println("Welcome", n)
}("Gophers")
}
// hello world first class function
// func()
// hello world first class function
// Welcome Gophers
3. User defined function types 自定义类型
package main
import (
"fmt"
)
type add func(a int, b int) int
func main() {
var a add = func(a int, b int) int {
return a + b
}
s := a(5, 6)
fmt.Println("Sum", s)
}
// Sum 11
4. Passing functions as arguments to other functions 将函数作为参数传递给其他函数
package main
import (
"fmt"
)
func simple(a func(a, b int) int) { // 接受一个名为 a 的参数,这个参数是一个函数类型,该函数接受两个整数参数并返回一个整数。
fmt.Println(a(60, 7)) // 在 simple 函数内部,它调用了传递进来的函数 a,并传递了两个整数参数 60 和 7。
}
func main() {
f := func(a, b int) int { //
return a + b // 接受两个整数参数并返回它们的和。
}
simple(f)
}
// 67
5. Returning functions from other functions 从其他函数返回函数
package main
import (
"fmt"
)
func simple() func(a, b int) int {
f := func(a, b int) int {
return a + b
}
return f
}
func main() {
s := simple()
fmt.Println(s(60, 7))
}
// 67
6. Closures闭包函数
package main
import "fmt"
func appendStr() func(string) string {
t := "hello"
c := func(b string) string {
t = t + " " + b
return t
}
return c
}
func main() {
a := appendStr()
b := appendStr()
fmt.Println(a("world"))
fmt.Println(b("everyone"))
fmt.Println(a("Gopher")) // 这个时候 已经变成 hello world + Gopher
fmt.Println(b("!"))
}
7. Practical use of first class functions
package main
import (
"fmt"
)
type student struct {
firstName string
lastName string
grade string
country string
}
func filter(s []student, f func(student) bool) []student {
var r []student
for _, v := range s {
if f(v) == true {
r = append(r, v)
}
}
return r
}
func main() {
s1 := student{
firstName: "Naveen",
lastName: "Ramanathan",
grade: "A",
country: "India",
}
s2 := student{
firstName: "Samuel",
lastName: "Johnson",
grade: "B",
country: "USA",
}
s := []student{s1, s2}
f := filter(s, func(s student) bool {
if s.grade == "B" {
return true
}
return false
})
fmt.Println(f)
}
// [{Samuel Johnson B USA}]
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请点点赞收藏+关注谢谢支持 !!!