Go Function, Methods

Gary Liao
7 min readMay 26, 2023

No Inheritance

Functions vs Methods

這是 function:

func myFunction(p paramType [,...]) returnType { }

這是 method:

func (r receiverType)myFunction(p paramType [,...]) returnType { }
  • Methods可以掛在幾乎任何type之上,除了pointer 與 interface。
  • Methods只能掛在 local type上 (同個package),所以無法直接掛在內建type 上,但可以使用 name type 的方式掛上去。
  • r 稱之為 receiver,就是概念上的物件。

Methods 不會使 type 變大

我們透過建立兩個 type: scoreA 與 scoreB,其中一個有 method,並比較兩者所產生變數的 memory size,結果是一樣大的。

package main

import (
"fmt"
"unsafe"
)

type scoreA int
type scoreB int

func (s scoreB) method() {}

func main() {
a := scoreA(100)
b := scoreB(100)
fmt.Println(unsafe.Sizeof(a)) // 8
fmt.Println(unsafe.Sizeof(b)) // 8
}

我的猜想:

Methods 其實底層與 Functions 沒什麼不同,只是在編譯前檢查語法,以及有沒有符合 interface 的限制。

Go Method 其實並不像其他語言的class method

Go 根本就沒有class,如果硬要套別種語言的概念來Go,會很痛苦。

正因為 method 的本質其實是 function,所以前面宣告 method 的方法,其實會複製一份 receiver,導致無法改動到 receiver 內部的值。

package main

import (
"fmt"
)

type Point struct{ X, Y int }

func (p Point) move() {
p.X += 1
fmt.Println(p, "in method")
}

func main() {
pt := Point{0, 0}
fmt.Println(pt) // {0 0}
pt.move() // {1 0} in method
fmt.Println(pt) // {0 0} <- 沒動
}

要改動 type 內部的值,還是得傳址:

package main

import (
"fmt"
)

type Point struct{ X, Y int }

func (p *Point) move() { // <- 傳址
(*p).X += 1
fmt.Println(*p, "in method")
}

func main() {
pt := Point{0, 0}
fmt.Println(pt) // {0 0}
(&pt).move() // {1 0} in method
fmt.Println(pt) // {1 0} <- 動了

pt.move() // {2 0} in method <- 編譯器施展魔法
fmt.Println(pt) // {2 0} <- 又動了
}

值得注意的是,改用傳址後,呼叫的方式應該為 (&pt).move() 才符合規則,但太冗,編譯器幫我們施展了魔法,所以 pt.move() 也行得通,也好閱讀。

繼承? 沒有這回事

如果把 struct 當作 class,透過巢狀 struct 就可以偽裝是 class 的繼承,但實際上不是。Go 沒有class,沒有 inheritance,而是 Composition (組合各種 type 形成新的 type),或是 struct embedding。

Composition 的動作,會使與底層 type 相依的 methods "看似" 被頂層 type 所 "繼承" ,因為可以直接被頂層 type 所引用,但那只是編譯器變魔術而已。假如上下曾有相同名稱的 field 或是 method ,則編譯器會採用上層的;若是同層發生這種情況,編譯器會報錯 (ambiguous selector),除非把整條 namespace 打完整。

package main

import "fmt"

type Color int32
type Age int8

const (
BLONDE Color = iota
WHITE
BLACK
)

type Suit struct {
Color
Age
}

type Shirt struct {
Color
Age
}

type Baby struct {
Name string
Color
Hair Color
Suit
Shirt
}

func (a *Age) Aging() string {
*a += 1
return fmt.Sprintf("I was %d years old, now I'm %d", *a-1, *a)
}

func main() {
baby := Baby{
Name: "Ted",
Color: WHITE,
Hair: BLOND,
Suit: Suit{BLACK, 3},
Shirt: Shirt{WHITE, 2}}
fmt.Println("baby.Color:", baby.Color) // baby.Color: 1
fmt.Println("baby.Hair:", baby.Hair) // baby.Hair: 0
fmt.Println("baby.Suit.Color:", baby.Suit.Color) // baby.Suit.Color: 2
fmt.Println("baby.Shirt.Color:", baby.Shirt.Color) // baby.Shirt.Color: 1

// fmt.Println("baby.Age:", baby.Age) // ambiguous selector baby.Age
fmt.Println("baby.Suit.Age:", baby.Suit.Age) // baby.Suit.Age: 3
fmt.Println("baby.Shirt.Age:", baby.Shirt.Age) // baby.Shirt.Age: 2
baby.Suit.Aging()
fmt.Println("baby.Suit.Age:", baby.Suit.Age) // baby.Suit.Age: 4
fmt.Println("baby.Shirt.Age:", baby.Shirt.Age) // baby.Shirt.Age: 2
}

想把 Method 當 Function 來用

雖然不能直接在把 method 變成 function,但可以透過如下語法把生成一個新的 funcion,其第一個參數就是原本 method 的 receiver:

 // (*T).f
funcAging := (&Age).Aging
a := Age(99)
fmt.Println(funcAging(&a)) // I was 99 years old, now I'm 100
fmt.Println(a) // 100

Encapsulation

開頭大寫 (Capitalized) 的variable 、 function 、 method 才能被其他package看到,想要有 private variable 的效果就得這樣幹。

--

--