Methods and Interfaces in Go
This post is a small cheatsheet for Methods and Interfaces in Go. Here I provide main features and describe what methods, interfaces, pointer receiver and value receiver are.
There are no detailed explanations or tricky examples about how to use them. For such content please see other resources.
At the end of the page, there is a small list of useful links to read
Methods
method
is a function with a special receiver
argument. Methods
provide a way to add behavior to (almost) any type.
Receiver
is a parameter between the func
keyword and the function name
. It binds the function to the specified type. It should be defined in the same package as the method.
A method cannot be declared with a receiver
whose type is defined in another package
(which includes the built-in types such as int).
A method may be declared either using a value
receiver or a pointer
receiver.
Methods with value
receiver:
- a literal syntax looks like
(t T)
- operate on a
copy of
the value - can be called with
pointer
values because they can bedereferenced
first
Methods with pointer
receiver:
- a literal syntax looks like
(t *T)
- operate on
actual value
- cannot be called with values, because the
value
stored inside an interfacehas no address
How to chose between pointer
and value
receiver:
- use
pointer
receiver formutations
or toavoid copying
the value on each method call because it may be an expensive operation. - use the
value
receiver to create new value/copy (without mutations)
Some more details on Method Sets specification page.
Example of a method with pointer receiver (is taken from the official A Tour of Go):
Interfaces
Interfaces
are types that declare behavior and provide polymorphism. There is no keyword for interface implementation in Go. So, a type
implements an interface
if it implements all interface methods.
It’s a so-named Duck typing interface.
Interfaces
in Go are reference
types or a header
value, the same as:
- set of slice
- map
- channel
- interface
- function types.
empty interface type
can represent any
type. It is common to use the empty interface type in
generic data structures. Empty interface
type allows both structure and primitive types to be stored.
MongoDB driver uses such approach for inserting documents into collection:
// InsertOne inserts a single document into the collection.
func (coll *Collection) InsertOne(ctx context.Context, document interface{},
opts ...*options.InsertOneOptions) (*InsertOneResult, error) {
...
}
To test whether an interface value holds a specific type, a type assertion
can be used:
t, ok := i.(T)
if i
is of type T
then t
will be the underlying value and ok
will be true
.
and for several type assertions Type switches
can be used:
package main
import "fmt"
type person struct {
name string
email string
}
func genericPrint(v interface{}) {
switch val := v.(type) {
case float64:
fmt.Printf("Pinting float64 %f\n", val)
case string:
fmt.Printf("Printing string %s\n", val)
case person:
fmt.Printf("Printing person with name: %s and email: %s", val.name, val.email)
default:
fmt.Printf("Printing default %v\n", val)
}
}
func main() {
genericPrint(123567890.787)
genericPrint("some string value")
genericPrint(123)
genericPrint(person{
name: "John Doe",
email: "[email protected]",
})
}
Interfaces and type embedding
Go doesn’t have class inheritance but has types embedding
within a struct or interface.
In scope of embedding there are inner
and outer
types:
Inner
type’s methods are promoted up to the outer
type. As a result, inner
’s type identifiers may be accessed through values of the outer
type.
Outer
types do not need to implement interface because of the inner
promotion. In other words, the outer
type implements the interface because of inner
type’s implementation.
In case if outer
type needs to override
the inner
type’s interface behavior, the outer
type should implement the interface.
Some more information can be found on Effective Go # Embedding