DEV Community

Ujjwal Goyal
Ujjwal Goyal

Posted on

Methods and Interfaces in Go

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))
}
Enter fullscreen mode Exit fullscreen mode

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())
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

Compilation error:

cannot use MyInt(6) (type MyInt) as type Operations in assignment:
MyInt does not implement Operations (missing tenfold method)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)