網頁

2021/6/27

Golang value receiver與pointer receiver差別

Go語言的receiver又分為value receiver與pointer receiver兩種,兩者區別如下。


Value receiver的型態前不加*,method的receiver為複製值;
Pointer receiver的型態前加*,method的receiver為指標。

下面AddTitle()方法為value receiver。在main()中被呼叫並修改了Employee.Name,但並沒有改變原本的employee.Name的值,因為AddTitle()的receiver (e Employee)為呼叫變數的複製值。

package main

import "fmt"

// define struct type 'Employee'
type Employee struct {
    Id int
    Name string
    Age int
}

// method of value receiver
func (e Employee) AddTitle(title string) {
    e.Name = title + e.Name // e is the copy value of variable 'employee'
}

func main() {
    employee := Employee{1, "John", 33}

    employee.AddTitle("Mr. ")
    fmt.Println(employee.Name) // John
}

若把AddTitle()的receiver型態前加上*則變成pointer receiver,執行結果則變成有修改到變數employee.Name的值。

package main

import "fmt"

type Employee struct {
    Id int
    Name string
    Age int
}

// method of pointer receiver
func (e *Employee) AddTitle(title string) {
    e.Name = title + e.Name // e is the pointer to variable 'employee'
}

func main() {
    employee := Employee{1, "John", 33}

    employee.AddTitle("Mr. ")
    fmt.Println(employee.Name) // Mr. John
}


此外實作interface的方法時,value type的值無法分派到pointer receiver實作的interface變數; 反之pointer type的值可以分派到value receiver實作的interface變數。

這是因為pointer type的method sets同時包含了pointer receiver及value receiver的methods;而value type的method sets只有value receiver的methods。節錄Go規格文件的Method sets

The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).

任意型態T的method sets包含了receiver T的全部methods。Pointer type *T的method sets包含了receiver *TT的全部methods


例如下面pointer receiver *Employee實作Worker interface的Work(),因此當把Employee value分派到Worker變數worker時會出現編譯錯誤(compile error)訊息如下。

cannot use (Employee literal) (value of type Employee) as Worker value in variable declaration: missing method Work (Work has pointer receiver)

main.go

package main

import "fmt"

type Worker interface {
    Work()
}

type Employee struct {
    Id   int
    Name string
    Age  int
}

// method of pointer receiver
func (e *Employee) Work() {
    fmt.Println(e.Name + " works")
}

func main() {
    var worker Worker = Employee{1, "John", 33} // compile error
    worker.Work()
}

因為Worker的實作為pointer receiver而非value receiver,因此只能接受pointer type的Employee值,因為才有包含pointer receiver的method。所以在Employee literal前加上&取得pointer即可正確分派到worker變數。

package main

import "fmt"

type Worker interface {
    Work()
}

type Employee struct {
    Id   int
    Name string
    Age  int
}

// method of pointer receiver
func (e *Employee) Work() {
    fmt.Println(e.Name + " works")
}

func main() {
    var worker Worker = &Employee{1, "John", 33} // assign pointer of Employee literal to worker
    worker.Work()
}

相反地,value receiver實作的interface變數則可同時接收value type或pointer type的值,因為pointer type的method sets同時包含了pointer receiver及value receiver的methods。注意分派pointer type值的worker變數呼叫的Work()方法仍是value receiver的method。

package main

import "fmt"

type Worker interface {
    Work()
}

type Employee struct {
    Id   int
    Name string
    Age  int
}

// method of value receiver
func (e Employee) Work() {
    fmt.Println(e.Name + " works")
}

func main() {
    var worker1 Worker = &Employee{1, "John", 33} // assign pointer of Employee literal to worker1
    worker1.Work()
    
    var worker2 Worker = Employee{2, "Mary", 28} // assign value of Employee literal to worker2
    worker2.Work()
}


沒有留言:

張貼留言