Tags: #go
Reference:
If a method defines its receiver as being a pointer, then the caller of the method ideally should pass a pointer as the receiver:
package main
import (
"fmt"
)
type foo struct{}
func (f *foo) bar() {
fmt.Printf("bar called on %T\n", f)
}
func main() {
f := &foo{}
f.bar() // bar called on *main.foo
}
Notice that we see
*main.foo
as the receiver was a pointer.
But as a convenience, the go compiler will automatically handle the passing of a pointer on behalf of the caller (in the case where they pass a value instead):
package main
import (
"fmt"
)
type foo struct{}
func (f *foo) bar() {
fmt.Printf("bar called on %T\n", f)
}
func main() {
f := &foo{}
(*f).bar() // bar called on *main.foo
}
In order to demonstrate the point, I first had to ‘dereference’ the pointer (*f)
to get at the ‘value’ before calling .bar()
on the struct.
The compiler thus saw (*f).bar()
and interpreted it as f.bar()
.
Remember that f
was already a pointer because when we created foo
we assigned the memory address location to f
using &foo{}
.
So this is why we still see the message bar called on *main.foo
.
The same principle happens in reverse (i.e. when you have a method whose receiver is a value and not a pointer).
So if a method defines its receiver as being a value, then the caller of the method ideally should pass a value as the receiver:
package main
import (
"fmt"
)
type foo struct{}
func (f foo) bar() {
fmt.Printf("bar called on %T\n", f)
}
func main() {
f := foo{}
f.bar() // bar called on main.foo
}
Notice now that instead of
*main.foo
we getmain.foo
as the receiver was a value and not a pointer.
But as a convenience, the go compiler will automatically handle the passing of a value on behalf of the caller (in the case where they pass a pointer instead):
package main
import (
"fmt"
)
type foo struct{}
func (f foo) bar() {
fmt.Printf("bar called on %T\n", f)
}
func main() {
f := foo{}
(&f).bar() // bar called on main.foo
}
In order to demonstrate the point, I first had to call (&f)
to get a ‘pointer’ before calling .bar()
on the struct.
The compiler thus saw (&f).bar()
and interpreted it as f.bar()
.
Remember that f
was already a ‘value’, because when we created foo
we didn’t assign the memory address like we did earlier (&foo{}
).
So this is why we still see the message bar called on main.foo
.
Note: this ‘convenience’ does not apply when dealing with functions, only methods.
In reality the confusion about what’s happening is greater, due to the more subtle differences.
For example, in the following code snippet the method expects a pointer receiver but when we create an instance of foo
we create a value and not a pointer. So when we call f.bar()
this is interpreted as (&f).bar()
:
package main
import (
"fmt"
)
type foo struct{}
func (f *foo) bar() {
fmt.Printf("bar called on %T\n", f)
}
func main() {
f := foo{}
f.bar() // bar called on *main.foo
}
Or how about that same snippet but in reverse, so the method expects a value receiver but when we create an instance of foo
we create a pointer. So when we call f.bar()
this is interpreted as (*f).bar()
:
package main
import (
"fmt"
)
type foo struct{}
func (f foo) bar() {
fmt.Printf("bar called on %T\n", f)
}
func main() {
f := &foo{}
f.bar() // bar called on main.foo
}
Sometimes you’re dealing with a type that isn’t a struct, like a slice, and so accessing the elements (for example to modify them) requires additional steps, and in some cases extra parentheses (as demonstrated/commented below)…
package main
import (
"fmt"
)
type foo []int
func (f *foo) pop() int {
// notice when we want to grab the length of the slice,
// and also when we want to grab a specific element from the slice,
// we have to first dereference the pointer to get at the underlying value.
//
v := (*f)[len(*f)-1]
// also notice to reassign a modified slice we again have to
// first dereference the pointer to get at the underlying value.
//
*f = (*f)[:len(*f)-1]
return v
}
func main() {
f := foo{9, 1, 6, 7, 0}
fmt.Println(f) // [9 1 6 7 0]
fmt.Println(f.pop()) // 0
fmt.Println(f) // [9 1 6 7]
fmt.Println(f.pop()) // 7
fmt.Println(f) // [9 1 6]
}