# Go Function --- ### 5.1 Function Declarations ---- ```go= func name(parameter-list) (result-list) { body } func hypot(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(3, 4)) // "5" // return one unnamed result // 或是沒有 return // 可以省略() ``` --- ### 5.2 Recursion ---- 遞迴 ---- ```go= golang.org/x/net/html package html type Node struct { Type NodeType Data string Attr []Attribute FirstChild, NextSibling *Node } type NodeType int32 const ( ErrorNode NodeType = iota TextNode DocumentNode ElementNode CommentNode DoctypeNode ) ``` ---- ```go= type Attribute struct { Key, Val string } func Parse(r io.Reader) (*Node, error) ``` ---- ```go= func main() { doc, err := html.Parse(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err) os.Exit(1) } for _, link := range visit(nil, doc) { fmt.Println(link) } } // visit appends to links each link found in n and returns the result. func visit(links []string, n *html.Node) []string { if n.Type == html.ElementNode && n.Data == "a" { for _, a := range n.Attr { if a.Key == "href" { links = append(links, a.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { links = visit(links, c) } return links } ``` --- ### 5.3 Multiple Return Values ---- 可以有多個 return 值 常見為 2 個 ex: data, err := doSomeThing() Note: 一個是期望的結果、一個是出錯的錯誤訊息 ---- Ex: findLinks ![](https://i.imgur.com/BbO51Tp.png) ---- continue * return 差異 * explicitly release resource ![](https://i.imgur.com/5OlCI03.png) Note: 第一個直接回傳,第23個有多做事情,最後一個回傳正確的值 並且 resp.Body 需要 close 雖然有 GC 但是不會去主動釋放未使用的系統資源例如打開文件或是網路連結,需要由我們顯性釋放 ---- Caller * 回傳是 tuple * explicitly assign * 使用 _ to ignore ```go= links, err := findLinks(url) links, _ := findLinks(url) // errors ignored ``` ---- 可被當作回傳值傳出去 ```go= func findLinksLog(url string) ([]string, error) { log.Printf("findLinks %s", url) return findLinks(url) } ``` Note: 只要傳出去的格式一致 ---- 也可當作參數傳入 ⚠️少使用在 Prod ```go= log.Println(findLinks(url)) // same effect links, err := findLinks(url) log.Println(links, err) ``` ---- result 有name 在多重回傳有一定意義 ```go= func Size(rect image.Rectangle) (width, height int) func Split(path string) (dir, file string) func HourMinSec(t time.Time) (hour, minute, second int) ``` ---- 慣例來說,下面 2 個不會特別給 name ```go= bool 代表是否成功 error 代表錯誤資訊 func doSomething() (bool, error) { // something } ``` ---- bare return (在很多回傳值很方便但不好閱讀) ```go= func CountWordsAndImages(url string) (words, images int, err error) { resp, err := http.Get(url) if err != nil { return //無需特別寫 return words, images, err } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { err = fmt.Errorf("parsing HTML: %s", err) return //無需特別寫 return words, images, err } words, images = countWordsAndImages(doc) return //無需特別寫 return words, images, err } ``` ---------------------------------------------------- ```go= return 0, 0, err return words, image, nil ``` --- ### 5.4 Error ---- 如果會回傳 error 通常會是最後一個 如果只有一種可能, the result is Bool 通常會是 ok ```go= value, ok := cache.Lookup(key) if !ok { // ...cache[key] does not exist... } ``` ---- The built-in type error is an interface type is nil -> 成功 isn't nil -> 失敗 ```go= // 看錯誤訊息 fmt.Println(err) fmt.Printf("%v", err) ``` ---- if return 風格: 為的就是關注如何錯誤處理 ```go= if error != nil { return } // 開發用 resp, _ := http.Get(url) ``` ---- 錯誤處理策略1 傳遞 ```go= resp, err := http.Get(url) if err != nil { return nil, err } ``` ---- 錯誤處理策略1 傳遞(加工) ```go= // 不應該大寫、不換行 doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return nil, fmt.Errorf("parsing %s as HTML: %v", url, err) } ``` ---- 錯誤處理策略2 Retry ```go= // WaitForServer attempts to contact the server of a URL. // It tries for one minute using exponential back-off. // It reports an error if all attempts fail. func WaitForServer(url string) error { const timeout = 1 * time.Minute deadline := time.Now().Add(timeout) for tries := 0; time.Now().Before(deadline); tries++ { _, err := http.Head(url) if err == nil { return nil // success } log.Printf("server not responding (%s); retrying...", err) time.Sleep(time.Second << uint(tries)) // exponential back-off } return fmt.Errorf("server %s failed to respond after %s", url, timeout) } ``` ---- 錯誤處理策略3 不可能的情況 優雅的停止~ 建議傳遞給主程式 ```go= // (In function main.) if err := WaitForServer(url); err != nil { fmt.Fprintf(os.Stderr, "Site is down: %v\n", err) os.Exit(1) } ``` ---- log.Fatalf 有時間 ```go= if err := WaitForServer(url); err != nil { log.Fatalf("Site is down: %v\n", err) } log.SetPrefix("wait: ") 加上 prefix log.SetFlags(0) 不顯示時間日期 // 2006/01/02 15:04:05 Site is down: no such domain: bad.gopl.io ``` ---- 錯誤處理策略4 show出 error 且繼續走 ```go= if err := Ping(); err != nil { log.Printf("ping failed: %v; networking disabled", err) } if err := Ping(); err != nil { fmt.Fprintf(os.Stderr, "ping failed: %v; networking disabledn", err) } // log 會自動換行 ``` ---- 錯誤處理策略5 安全的完全忽略 ```go= dir, err := ioutil.TempDir("", "scratch") if err != nil { return fmt.Errorf("failed to create temp dir: %v", err) } // ...use temp dir... os.RemoveAll(dir) // ignore errors; $TMPDIR is cleaned periodically ``` ---- End of File (EOF) ```go= package io import "errors" // EOF is the error returned by Read when no more input is available. var EOF = errors.New("EOF") in := bufio.NewReader(os.Stdin) for { r, _, err := in.ReadRune() if err == io.EOF { break // finished reading } if err != nil { return fmt.Errorf("read failed: %v", err) } // ...use r... } ``` --- ### 5.5 Function Values ---- Functions are first-class values in Go * 可以做 variable * 可以做 參數 * 可以做 回傳值 ---- Ex ```go= func square(n int) int { return n * n } func negative(n int) int { return -n } func product(m, n int) int { return m * n } f := square fmt.Println(f(3)) // "9" f = negative fmt.Println(f(3)) // "-3" fmt.Printf("%T\n", f) // "func(int) int" f = product // compile error: can't assign func(int, int) int to func(int) int ``` ---- func default value is nil ```go= var f func(int) int f(3) // panic: call of nil function // 僅能跟 nil 比較 var f func(int) int if f != nil { f(3) } ``` ---- 除了數據化也能行為化 ```go= func add1(r rune) rune { return r + 1 } fmt.Println(strings.Map(add1, "HAL-9000")) // "IBM.:111" fmt.Println(strings.Map(add1, "VMS")) // "WNT" fmt.Println(strings.Map(add1, "Admix")) // "Benjy" ``` ---- --- ### 5.6 Anonymous Functions ---- 匿名函式可使用 func 建立,同樣必須指定參數與傳回值型態 ```go= 不行 func funcA() { func funcB() { ... } ... } 可以 func funcA() { funcB := func() { ... } ... } ``` ---- 沒有名稱 ```go= // 匿名 strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000") // 有名 func add1(r rune) rune { return r + 1 } strings.Map(add1, "HAL-9000") // "IBM.:111" ``` ---- closure 閉包將變數本身關閉在自己的範疇中,而不是變數的值 ```go= // squares returns a function that returns // the next square number each time it is called. func squares() func() int { var x int return func() int { x++ return x * x } } func main() { f := squares() fmt.Println(f()) // "1" fmt.Println(f()) // "4" fmt.Println(f()) // "9" fmt.Println(f()) // "16" } ``` ---- ![](https://i.imgur.com/G0Pdedk.png) ---- ![](https://i.imgur.com/GM2WRSi.png) ---- ![](https://i.imgur.com/lHUuK3L.png) ---- --- ### 5.7 Variadic Functions Note: 可變函數 ---- Ex: fmt.Printf 接受第一個必備參數後,後面接任意數量的參數 ```go= func Printf(format string, a ...interface{}) (n int, err error) fmt.Printf("%s is %d years old.\n", name, age) ``` ---- 宣告:最後一個參數類型之前加上省略符號 ... ```go= func sum(vals ...int) int { total := 0 for _, val := range vals { total += val } return total } ``` ---- 在函數體中,vals被看作是類型爲[] int的切片 ```go= fmt.Println(sum()) // "0" fmt.Println(sum(3)) // "3" fmt.Println(sum(1, 2, 3, 4)) // "10" ``` ---- implicit create slice copy argument to slice for func ```go= values := []int{1, 2, 3, 4} fmt.Println(sum(values...)) // "10" fmt.Println(sum(1, 2, 3, 4)) // "10" ``` Note: 如果原始參數已經是切片類型,我們該如何傳遞給sum?隻需在最後一個參數後加上省略符。下面的代碼功能與上個例子中最後一條語句相同。 ---- difference with slice ```go= func f(...int) {} func g([]int) {} fmt.Printf("%T\n", f) // "func(...int)" fmt.Printf("%T\n", g) // "func([]int)" ``` ---- 常用來格式化文字 ```go= // Ex: 後綴f是一種通用的命名規範, // 代表該可變參數函數可以接收Printf風格的格式化字符串。 func errorf(linenum int, format string, args ...interface{}) { fmt.Fprintf(os.Stderr, "Line %d: ", linenum) fmt.Fprintf(os.Stderr, format, args...) fmt.Fprintln(os.Stderr) } linenum, name := 12, "count" errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count" // interfac{} 表示函數的最後一個參數可以接收任意類型 ``` --- ### 5.8 Deferred Function Calls ---- keyword: defer 執行時間: End or Panic, before **return** 順序: LIFO ---- ```go= func main() { defer fmt.Println("deffered 1") defer fmt.Println("deffered 2") fmt.Println("Hello, 世界") } ``` ---- ```go= // Hello, 世界 // deffered 2 // deffered 1 ``` ---- 用途1: 釋放資源 ---- ![](https://i.imgur.com/1MhE5qV.png) ---- ![](https://i.imgur.com/D0MIDAR.png) ---- 用途2: debug(entry\exit) trace 範例解釋 ![](https://i.imgur.com/GZ94IWn.png) Note: 下面的 bigSlowOperation 函數立即調用 trace,它執行“進入時”操作,然後返回一個函數值,當調用時,執行相應的“退出時”操作。 通過以這種方式推遲對返回函數的調用,我們可以在單個語句中檢測函數的入口點和所有出口點,甚至可以在兩個操作之間傳遞值,例如開始時間。 但是不要忘記 defer 語句中的最後一個括號,否則“on entry”動作將在退出時發生而 on-exit 動作根本不會發生! --- ### 5.9 Panic ---- ![](https://i.imgur.com/TTRByNf.png) ---- #### Panic With Defer ---- 當 Panic 發生時 1. 直接停止🤚,不再執行剩餘的 code 2. 所有 defer 開始執行(FILO) 3. 類似 stack 4. 直到 main func ---- 因為會 panic 發生往上傳遞時, 一路上的 defer 都會被呼叫 進而衍生許多好用的方法 ---- #### 實際 Panic 發生例子 ![](https://i.imgur.com/mjoWJ7K.png) ---- 接續 ![](https://i.imgur.com/jI2I7yP.png) ---- #### 利用 Panic 發生觸發 defer 特性 搭配 runtime.Stack ![](https://i.imgur.com/YBjHgGr.png) ---- 接續 ![](https://i.imgur.com/iCrPb35.png) --- ### 5.10 Recover ---- 為了不要 crash 且想獲取部份資訊。 ```go= func Parse(input string) (s *Syntax, err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error: %v", p) } }() // ...parser... } ``` ---- 1. panic 要在 defer 後(前) ```go= func main() { panic("a") defer func() { fmt.Println("b") }() } ------------------- panic: a goroutine 1 [running]: main.main() /xxxxx/src/xxx.go:50 +0x39 exit status 2 ``` ---- 1. panic 要在 defer 後(後) ```go= func main() { defer func() { fmt.Println("b") }() panic("a") } ------------- b panic: a goroutine 1 [running]: main.main() /xxxxx/src/xxx.go:50 +0x39 exit status 2 ``` ---- 2. F 發生 Panic 並有 Recover ```go= func G() { defer func() { fmt.Println("c") }() F() fmt.Println("繼續執行") } func F() { defer func() { if err := recover(); err != nil { fmt.Println("捕獲error:", err) } fmt.Println("b") }() panic("a") } ------- 捕獲error: a b 繼續執行 c ``` Note: F中出現panic時,F函數會立刻終止,不會執行F函數內panic後面的內容,但不會立刻return,而是調用F的defer,如果F的defer中有recover捕獲,則F在執行完defer後正常返回,調用函數F的函數G繼續正常執行 ---- 3. F 發生 Panic,在 G 中Recover ```go= func G() { defer func() { if err := recover(); err != nil { fmt.Println("捕獲異常:", err) } fmt.Println("c") }() F() fmt.Println("繼續執行") } func F() { defer func() { fmt.Println("b") }() panic("a") } -------- b 捕獲異常: a c ``` Note: 如果F的defer中無recover捕獲,則將panic拋到G中,G函數會立刻終止,不會執行G函數內後面的內容,但不會立刻return,而調用G的defer...以此類推 ---- 4. F 發生 Panic 但都沒有 Recover ```go= func G() { defer func() { fmt.Println("c") }() F() fmt.Println("繼續執行") } func F() { defer func() { fmt.Println("b") }() panic("a") } ---------- b c panic: a goroutine 1 [running]: main.F() /xxxxx/src/xxx.go:61 +0x55 main.G() /xxxxx/src/xxx.go:53 +0x42 exit status 2 ``` Note: 如果一直沒有recover,拋出的panic到當前goroutine最上層函數時,程序直接異常終止 ----
{"metaMigratedAt":"2023-06-16T10:19:01.004Z","metaMigratedFrom":"Content","title":"Go Function","breaks":true,"contributors":"[{\"id\":\"e778c849-0740-4878-b20c-726784582289\",\"add\":14431,\"del\":1982}]"}
    230 views