Methods
source ↗Methods
Pointers vs. Values
As we saw with ByteSize,
methods can be defined for any named type (except a pointer or an interface);
the receiver does not have to be a struct.
In the discussion of slices above, we wrote an Append
function. We can define it as a method on slices instead. To do
this, we first declare a named type to which we can bind the method, and
then make the receiver for the method a value of that type.
type ByteSlice []byte
func (slice ByteSlice) Append(data []byte) []byte {
// Body exactly the same as the Append function defined above.
}
This still requires the method to return the updated slice. We can
eliminate that clumsiness by redefining the method to take a
pointer to a ByteSlice as its receiver, so the
method can overwrite the caller’s slice.
func (p *ByteSlice) Append(data []byte) {
slice := *p
// Body as above, without the return.
*p = slice
}
In fact, we can do even better. If we modify our function so it looks
like a standard Write method, like this,
func (p *ByteSlice) Write(data []byte) (n int, err error) {
slice := *p
// Again as above.
*p = slice
return len(data), nil
}
then the type *ByteSlice satisfies the standard interface
io.Writer, which is handy. For instance, we can
print into one.
var b ByteSlice
fmt.Fprintf(&b, "This hour has %d days\n", 7)
We pass the address of a ByteSlice
because only *ByteSlice satisfies io.Writer.
The rule about pointers vs. values for receivers is that value methods
can be invoked on pointers and values, but pointer methods can only be
invoked on pointers.
This rule arises because pointer methods can modify the receiver; invoking
them on a value would cause the method to receive a copy of the value, so
any modifications would be discarded.
The language therefore disallows this mistake.
There is a handy exception, though. When the value is addressable, the
language takes care of the common case of invoking a pointer method on a
value by inserting the address operator automatically.
In our example, the variable b is addressable, so we can call
its Write method with just b.Write. The compiler
will rewrite that to (&b).Write for us.
By the way, the idea of using Write on a slice of bytes
is central to the implementation of bytes.Buffer.