Some notes on Receivers
GoLangReview 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
.