===============
== CyberJunk ==
===============
Made with 😭

Some notes on Receivers

GoLang

Review of receivers

When a value receiver is defined, Go automatically generates a corresponding pointer receiver.

Auto-generating a pointer receiver causes differences in the method sets of pointer types and value types.

For example, take the Len() method below, Go will automatically generate a corresponding pointer receiver version:

type List []int

func (l List) Len() int { return len(l) }
// func (l *List) Len() int { ... }
func (l *List) Append(val int) { *l = append(*l, val) }

For type List, its method set includes:

  • Len() int

For type *List, the method set includes:

  • Len() int
  • Append(int)

This difference affects the use of interfaces:

type Container interface {
    Len() int
    Append(int)
}
var _ Container = List{} // no
var _ Container = &List{}

However, for the type’s own pointer and value, there appears to be no difference, both can call the two methods:

l := List{}
pl := &List{}

// List only implements `Len`
l.Append(42) // how does Go find `Append`?
// (&l).Append(42)
l.Len() // gets 1

// *List implements `Append` explicitly, `Len` implicitly
pl.Append(6)
pl.Len() // gets 1

In the List method set, there is no Append(int). Go converts the corresponding call to (&l).Append(42) to implement this which is a syntactic sugar.

However, if you cannot get the address of an object of a certain type, this feature does not work. Non-addressable types include map and interface and some other, as shown below:

m := map[string]List{"list1": List{}}
p := &m["list1"] // cannot take the address of m["list1"] (map index expression of type List)

var i interface{} = List{}
p = &i.(List) // cannot take the address of i.(List)

Because the map stores List, you can directly call List.Len(). Since you cannot take the address of a map element, you cannot convert it to call *List.Append(). The same applies to interface:

fmt.Println(m["list1"].Len()) // ok
m["list1"].Append(42) // bad, cannot call the pointer method Append on List

i.(List).Len() // ok
i.(List).Append() // bad, cannot call the pointer method Append on List

What Do Auto-Generated Pointer Receivers Do

If the auto-generated pointer receiver’s function body is the same as the value receiver, can I modify the object’s value through the pointer? No. In fact, the pointer receiver calls the value receiver and passes arguments by value.

When using dlv debug main.go to debug main.(*List).Len, I found that it doesn’t stop at this function breakpoint. Reviewing the previous code, pl.Len() may have been converted to (*pl).Len(), which caused it to use the value receiver. Therefore, it is necessary to forcefully use the pointer receiver:

l := List{}
pl := &List{}

// List only implements `Len`
l.Append(42) // how does Go find `Append`?
// (&l).Append(42)
l.Len() // gets 1

// *List implements `Append` explicitly, `Len` implicitly
pl.Append(6)
pl.Len() // gets 1

// Forcefully call `func(l *List) Len()`
(*List).Len(pl) // gets 1

It is confirmed that main.(*List).Len internally calls main.List.Len.