In the last blog, we looked at functions in Go. Let's move 1 step forward.
Methods and receivers
Consider the program
package main
import (
"fmt"
)
type Point struct {
x, y int
}
func tenfold(p Point) Point {
return Point{p.x * 10, p.y * 10}
}
func main() {
q := Point{4, 5}
fmt.Println(tenfold(q))
}
This prints {40 50}
as the answer.
This function can be rewritten as a method. A method in Go is a function with a special receiver argument (more about receivers in a moment). The above function can be written as
func (p Point) tenfold() Point {
return Point{p.x * 10, p.y * 10}
}
func main() {
q := Point{4, 5}
fmt.Println(q.tenfold())
}
Here, the tenfold method has a receiver of type Point named p. This function prints the same result of {40 50}
.
Methods are not limited to receivers of struct types. The only condition for the receiver is that the receiver type must be defined in the same package as the method. Thus, built-in types like int
or float64
cannot be used directly as receiver types. To do so, define a custom type first
type MyInt int
Now, you can use MyInt
as a receiver which will take an integer value.
Note: Functions with a pointer/value argument can only take a pointer/value respectively, but methods with a pointer/value receiver can take either a pointer or value.
Interfaces
In the above example, tenfold() Point
is defined as a method signature. This leads into the next segment, interfaces. Interfaces are abstract types that are a set of method signatures. Values of these interface types can have any value that implements all the methods in that interface. Phew, that's enough of theory, now let's look at some code to understand interfaces.
package main
import (
"fmt"
)
type Point struct {
x, y int
}
type MyInt int
type Operations interface {
double()
}
func (p Point) double() {
fmt.Println(Point{p.x * 2, p.y * 2})
}
func (number MyInt) double() {
fmt.Println(number * 2)
}
func main() {
var q Operations = Point{4, 5}
var n Operations = MyInt(6)
q.double()
n.double()
}
We have defined the double()
method on both the Point
and the MyInt
types, and added the method to the Operations
interface. We declare variables q
and n
to be of type Operations, and assign a Point and MyInt value to them respectively. We call the double() method, giving the output
{8 10}
12
The part (p Point) double()
indicates that type Point implements the Operations interface (as the interface has the double
method), but this does not need to be declared explicitly. Thus, a type implements an interface by implementing its methods.
If we were to add the tenfold()
method from before to our code, but only define it for the Point type, we would get a compilation error saying MyInt does not implement Operations (missing tenfold method)
.
type Operations interface {
tenfold() Point
double()
}
func (p Point) tenfold() Point {
return Point{p.x * 10, p.y * 10}
}
func (p Point) double() {
fmt.Println(Point{p.x * 2, p.y * 2})
}
func (number MyInt) double() {
fmt.Println(number * 2)
}
func main() {
var q Operations = Point{4, 5}
var n Operations = MyInt(6)
fmt.Println(q.tenfold())
q.double()
n.double()
}
Compilation error:
cannot use MyInt(6) (type MyInt) as type Operations in assignment:
MyInt does not implement Operations (missing tenfold method)
Thus, for a type to implement an interface, all the methods of that interface have to be defined for that particular type.
Note: Not all interface methods have to be called. For example, the fmt
package has a Stringer interface:
type Stringer interface {
String() string
}
From the documentation:
"If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any)."
Thus, if the String() string
method is defined, the print functions will invoke the Stringer interface automatically, i.e. without explicitly calling the String() method. Click here for an example of the Stringer interface in action.
Further reading:
Top comments (0)