在值上使用方法和在指针上使用方法有什么区别?

I want to know whats the difference of having methods on pointer and method on values. How both the methods work on standard structure instance as well as structure pointer.


Define receiver as value

Format:

func (r T) Xxx() {}

Could call by either a value or a pointer.

When call with pointer, the value will be passed automatically, (it actually use * to get value of the caller, and pass it).


Define receiver as pointer

Format:

func (r *T) Xxx() {}

In principle, should invoke with pointer only, but that's not necessary.

Because when call with value, instead of pointer, compiler will take care of it, when possible:

  • If the value is addressable, (which is true for most data type in go).
    Then compiler will take the address (via &), and pass it automatically.
    This enable to call a pointer method with value directly, (this is pretty common in go I guess, and it makes programmer's life easier).
  • If the value is not addressable, (which is rare, but exists).
    Then need to pass the address explicitly, otherwise would get error when compile.
    e.g map's element is not addressable.

Tips

  • Pointer caller is preferred, when define a method, if proper.
    Reasons:

    • It could modify the caller.
    • It's more lightweight for a complex caller.
  • What is passed to method, depends on the method signature, not how you call it (this is similar as with param).

    • When declare caller as pointer (r *T), it pass pointer.
    • When declare caller as value (r T), it pass a copy of original caller.
  • T itself can't be pointer.

Code

And, here is a go code that I wrote when learning this feature.

Its 2 functions called in main() tests the 2 features respectively.

method_learn.go:

// method - test
package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    x float64
    y float64
}

// abs, with pointer caller,
func (v *Vertex) AbsPointer() float64 {
    return math.Sqrt(v.x*v.x + v.y*v.y)
}

// scale, with pointer caller,
func (v *Vertex) ScalePointer(f float64) *Vertex {
    v.x = v.x * f
    v.y = v.y * f

    return v
}

// abs, with value caller,
func (v Vertex) AbsValue() float64 {
    return math.Sqrt(v.x*v.x + v.y*v.y)
}

// test - method with pointer caller,
func pointerCallerLearn() {
    vt := Vertex{3, 4}
    fmt.Printf("Abs of %v is %v. (Call %s method, with %s)
", vt, vt.AbsPointer(), "pointer", "value")        // call pointer method, with value,
    fmt.Printf("Abs of %v is %v. (Call %s method, with %s)

", vt, (&vt).AbsPointer(), "pointer", "pointer") // call pointer method, with pointer,

    // scala, change original caller,
    fmt.Printf("%v scale by 10 is: %v (Call %s method, with %s)
", vt, vt.ScalePointer(10), "pointer", "value")      // call pointer method, with value,
    fmt.Printf("%v scale by 10 is: %v (Call %s method, with %s)
", vt, (&vt).ScalePointer(10), "pointer", "pointer") // call pointer method, with pointer,
}

// test - method with value caller,
func valueCallerLearn() {
    vt := Vertex{3, 4}
    fmt.Printf("Abs of %v is %v. (Call %s method, with %s)
", vt, (&vt).AbsValue(), "value", "pointer") // call value method, with pointer,
    fmt.Printf("Abs of %v is %v. (Call %s method, with %s)
", vt, vt.AbsValue(), "value", "value")      // call value method, with value,
}

func main() {
    // pointerCallerLearn()
    valueCallerLearn()
}

Just modify main(), and run via go run method_test.go, then check the output to see how it works.

The big difference between them is that value receivers are copied*. So if you want to mutate your receiver, you have to use pointer. Observe:

package main

import (
    "fmt"
)

type Person struct {
  Age int
}

func (p Person) GrowUp1() {
  p.Age++
}

func (p *Person) GrowUp2() {
  p.Age++
}

func main() {
  p := Person{Age: 20}
  fmt.Println(p)

  p.GrowUp1()
  fmt.Println(p)


  p.GrowUp2()
  fmt.Println(p)
}
// {20}
// {20}
// {21}

* Pointers are copied too, naturally. But since they're pointers, a copy of a pointer still points to the same object.