# \[Golang]: 進階用法

主要介紹在 Golang 中相對進階的用法，如interface、reflection、Tag。善用這些技巧可以使得程式碼更加簡潔。ex, 透過 interface 的技巧使得 func 的參數更加有彈性；使用 reflection 進一步資料屬於的型態、甚至達到無須知道 type 也能夠修改資料; Tag 讓你 mapping 資料更加方便。

### Golang Interface

#### `Interface{} 型別轉換`

在Golang 中`Interface`資料結構是相當重要的，由於 Golang 屬於強型別語言，因此在func 中的 parameter 與 return value 時常會因為結構受限造成許多的不方便，然而`interface`就是來解決問題。(它本身可以是Golang 語言中任一 `type` 進而解決該問題。) [@in play ground](https://play.golang.org/p/zaGM7uRrL54)

```go
package main

import (
	"fmt"
	"strconv"
)

type str string

func (s str) String() string {
	return string(s)
}

type Stringer interface {
	String() string
}

func ToString(any interface{}) string {
	if v, ok := any.(Stringer); ok {
		return v.String()
	}
	switch v := any.(type) {
	case int:
		return strconv.Itoa(v)
	case float64:
		return strconv.FormatFloat(v, 'g', -1, 64)
	}
	return "???"
}

func main() {
	var ex int = 1
	fmt.Println(ToString(ex))
	
	var ex2 float64 = 0.1
	fmt.Println(ToString(ex2))

	var ex3 Stringer = str("1")
	fmt.Println(ToString(ex3))
}

```

如上述程式碼，實作ToString 方法時，需要傳入各種型態(type)的參數，此時Interface 的彈性就派上用場了，藉由還原的語法進行實作，繞過強型別參數型態固定的問題。

#### `Interface{} 多形`

此外在Golang中若要做到 abstract method 的話則也需要 `interface`，它提供抽象方法的功能，並且可以在compile time 就能排除抽象方法實作上部份錯誤，ex. 少定義方法等...。換句話說，在Golang中實作`多型` 須仰賴`interface`。[@in play ground](https://play.golang.org/p/7QXmubY_W7-)

```go
package main

import (
	"fmt"
)


type Car interface {
	AddOil(gallon int)
	Run()
}

type VovoCar struct {
}

func (v *VovoCar) AddOil(gallon int) {
	fmt.Printf("vovo car add %d gallon\n", gallon)
}

func (v *VovoCar) Run() {
	fmt.Printf("vovo car add run\n")
}

type ToyotaCar struct {
}

func (t *ToyotaCar) AddOil(gallon int) {
	fmt.Printf("Toyota car add %d gallon\n", gallon)
}

func (t *ToyotaCar) Run() {
	fmt.Printf("Toyota car add run\n")
}

func main() {
	var c Car = &VovoCar{}
	var c2 Car = &ToyotaCar{}
	c.AddOil(10)
	c.Run()

	c2.AddOil(100)
	c2.Run()
}

```

### Golang Reflection

`Reflection` 是一種用於描述程式語言的工具。由於在Golang 中任何型別都可以是一種`Interface`，因此時常需要與`Reflection` 進行搭配，故筆者認為是一種`Interface`的配套工具。主要有三種用法：

* 映射出Interface 的資料型別 [@playground](https://play.golang.org/p/UVcH0UziXqQ)

```go
package main

import (
	"fmt"
	"reflect"
)

type Example struct {}

func main() {
	fmt.Println(reflect.TypeOf(&Example{}))	// *main.Eaxmple
}

```

* &#x20;映射出Interface 的值 [@playground](https://play.golang.org/p/6Cyl-5xRV4t)

```go
package main

import (
	"fmt"
	"reflect"
)

type Example struct {
	name string
}

func main() {
	val := reflect.ValueOf(&Example{ name: "Example name"}).Interface().(*Example) 
	fmt.Println(val) // &{Example name}
}
```

* 將某interface 資料注入到其他interface 中。 [@palyground](https://play.golang.org/p/N2wSMbc6R22)
  * 主要透過Call by Reference 的原理進行修改，因此dest 型態必須為`Ptr`。
  * 搭配`reflect.ValueOf.Elem.Field` 找出struct filed 的位置進而修改資料。

```go
package main

import (
	"fmt"
	"reflect"
)

type Example struct {
	Id   string
	Name string
}

func ChangeValue(dest interface{}) {
	valDest := reflect.ValueOf(dest)

	for i := 0; i < valDest.Elem().NumField(); i++ {
		if i == 0 {
			valDest.Elem().Field(i).Set(reflect.ValueOf("change_id"))
		} else {
			valDest.Elem().Field(i).Set(reflect.ValueOf("change_name"))
		}
	}
}

func main() {
	destVal := &Example{Id: "test_id", Name: "test_name"}
	fmt.Println(destVal) // &{test_id test_name}
	ChangeValue(destVal)
	fmt.Println(destVal) // &{change_id change_name}
}

```

### Golang Tag

在Golang Struct 資料結構中，***可自定義 Tag***，有點類似於其他語言的Annotation，例如在判讀 Json 的Key 值時，須利用`` `json:" ``name`` "` ``的方式填入。然而在學習Golang 的初期時常會勿以為 Tag 是不可定義的，因為並不清楚如何取用。然而Reflection 工具此時就得到了一大作用，由於是描述程式語言的工具，因此可透過該工具將Tag 取出。[@playground](https://play.golang.org/p/jlsJMzriKzX)

```go
package main

import (
	"fmt"
	"reflect"
)

type Example struct {
	Id string `json:"id"`
}

func main() {
	jsonExample := &Example{Id: "test"}
	field := reflect.TypeOf(*jsonExample).Field(0)
	fmt.Printf("name: %v ,tag:'%v'\n", field.Name, field.Tag) 
	// name: Id ,tag:'json:"id"'
}


```

### Golang 低階指標用法 ( uintptr, unsafe.Pointer)

在Golang 語言中除了正規的正常的寫法之外，也提供低階的程式設計模式，如可直接調用Unix 系統的C程式的cgo。但這種方式是不被建議使用的，因為直接調用很容易出現非預期的錯誤，除非特殊需要。若要Golang 中使用這一類低階的程式設計，則需宣告`import "unsafe"`字樣，語意上表示從外部匯入unsafe 套件，但實際上是由編譯器直接調用隱藏功能。`ex: net, syscall, os, runtime 等大多程式設計不太需要用到的。`

* **uintptr**  屬於Golang 基礎型態的一種，事實上是 Integer 型別，且可以接各種型態的指標。
* **unsafe.Pointer** 是將任何型別的東西轉換成`*ArbitraryType` 而 ArbitraryType 的定義為`type ArbitraryType int` ，因此實際上也是一種 Integer 型別。

#### Escape Analysis 演算法

指的是在編譯器端，對於程式碼編譯後，針對指標 (Pointer) 資料結構進行優化，並計算出需要多少的Heap 儲存。往往在Golang 中，時常使用到指標類型的結構，然而只要有該函式之外的呼叫，編譯器則會預先預先多配發空間，進而觸發GC(Grabage Collection)機制。

例如以下圖式為例: 單純透過指標的方式印出整數陣列，正規寫法如Listing 1 直接使用外部func 印出，優化做法透過 unsafe 的方式呼叫外部func。

![](/files/-MkaGo8x_R4uyVSSHclp)

在Golang中使用unsafe 直接調用Compile 的Pointer編譯器則不會預先產出Heap，也因此能避免該問題，**但壞處是可能會遇到預期之外的錯誤(*****警語*****)**。如上圖程式碼，都只是印出數字，但透過 unsafe pointer 調用的，使用的記憶體大小是預設的50倍，Heap 的大小是 3倍(下圖所示)。

![](/files/-MkaDhfolvBaihANxu6S)

### Reference

\[1] Wang, Cong, et al. "Escape from escape analysis of Golang." Proceedings of the ACM/IEEE 42nd International Conference on Software Engineering: Software Engineering in Practice. 2020.

\[2] <https://go.dev/blog/laws-of-reflection>

\[3] <https://research.swtch.com/interfaces>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xiang753017.gitbook.io/zixiang-blog/golang/draft-golang-unsafe-pkg-zuo-yong-yu-yong-fa.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
