# 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

----
continue
* return 差異
* explicitly release resource

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"
}
```
----

----

----

----
---
### 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: 釋放資源
----

----

----
用途2: debug(entry\exit)
trace 範例解釋

Note: 下面的 bigSlowOperation 函數立即調用 trace,它執行“進入時”操作,然後返回一個函數值,當調用時,執行相應的“退出時”操作。 通過以這種方式推遲對返回函數的調用,我們可以在單個語句中檢測函數的入口點和所有出口點,甚至可以在兩個操作之間傳遞值,例如開始時間。 但是不要忘記 defer 語句中的最後一個括號,否則“on entry”動作將在退出時發生而 on-exit 動作根本不會發生!
---
### 5.9 Panic
----

----
#### Panic With Defer
----
當 Panic 發生時
1. 直接停止🤚,不再執行剩餘的 code
2. 所有 defer 開始執行(FILO)
3. 類似 stack
4. 直到 main func
----
因為會 panic 發生往上傳遞時,
一路上的 defer 都會被呼叫
進而衍生許多好用的方法
----
#### 實際 Panic 發生例子

----
接續

----
#### 利用 Panic 發生觸發 defer 特性
搭配 runtime.Stack

----
接續

---
### 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}]"}