Video Coming Soon...

Created by Zed A. Shaw Updated 2025-10-07 14:32:54

23: Struct and Methods: Interfaces

WARNING This exercise is in DRAFT status, so there may be errors. If you find any, please email me at help@learncodethehardway.com so I can fix them.

We're now going to create a better "universal Fruit and Animal" printer using the interfaces feature of Go. You can think of an interface as a protocol that says, "If a type has this method, then I can use that type without knowing anything else about it." The Stringer interface is the prime example of this:

type Stringer interface {
    String() string
}

This looks similar to a type X struct but you use type X interface instead. When you do this Go creates a "fake type" that is valid as long as the target struct implements a String() string function. For example, if I want my Fruit to follow the Stringer protocol then I could have this:

func (fruit Fruit) String() string {
    return Fruit.Name
}

DODGY BULLSHIT WARNING I have to more deeply research this as I think this is why but I actually can't find a solid explanation.

You may notice that (fruit Fruit) doesn't have a pointer syntax (fruit *Fruit). This is because an interface is implemented as a "fat pointer" which is just a pointer with extra gear, and adding another * on it would cause problems when you use the interface. You'd have to do extra to use a String() string that also did (fruit *Fruit), so just remember:

When it's an interface, don't use a "method pointer style" function.`.

Interfaces vs. type switch

This is why I called that function in Exercise 22 a BadPrinter. If you need to change the behavior depending on the type it's better to use an interface because as you add more types you'll have to constantly expand the switch.

With an interface you just make a function that does the thing and that's it. Need to fix the function, go to the implementation of the interface rather than digging around in the guts of some 7000 lines long switch statement.

If that's the case then what are type switch good for? I usually see them in systems that have events you need to handle differently. In the tcell package it's used to indicate if an event is a keyboard, mouse, or screen resize event.

In that case an interface won't help much because your program has to take what tcell gives you and make a decision based on it. The alternative for tcell would be a large number of int constants, but then you couldn't get the event data easily from tcell. A type switch that decides on the type and also gets the value is far better.

The Code

This code demonstrates using an interface to print Fruit and Animal types:

View Source file ex23/main.go Only

package main

import (
    "fmt"
)

type Printer interface {
    Print()
}

type Animal struct {
    Name string
    Age int
    Species string
    Weight float64
}

type Fruit struct {
    Name string
    Taste string
    Smell string
}

func (what Animal) Print() {
    fmt.Println("-- Animal named", what.Name)
    fmt.Println(what.Name, "is", what.Age, "years old.")
    fmt.Println(what.Name, "is a", what.Species)
    fmt.Println(what.Name, "weighs", what.Weight)
}

func (what Fruit) Print() {
    fmt.Println("-- Fruit named", what.Name)
    fmt.Println(what.Name, "tasts like", what.Taste)
    fmt.Println(what.Name, "smells like", what.Smell)
}

func PrintThing(out Printer) {
    out.Print()
}

func main() {
    joey := Animal{"Joey", 3, "Kangaroo", 100.0}
    PrintThing(joey)

    durian := Fruit{"Durian", "Heaven", "Dead bodies and onions."}
    PrintThing(durian)
}

The Breakdown

  1. type Printer interface { -- This creates an interface named Printer. Anything that has these functions will be considered useable as a Printer.
  2. Print() -- The only funtion that Printer requires is a Print() function.
  3. } -- This ends the Printer interface.
  4. func (what Animal) Print() { -- We define the Print() for Animal and that is all we need for it to work with the Printer interface.
  5. func (what Fruit) Print() { -- Same but for Fruit.
  6. func PrintThing(out Printer) { -- This is a function that takes anything that implements the Printer interface. As long as your struct has a method named Print() then PrintThing knows how to use it.
  7. out.Print() -- Here's where we call Print(), and Go figures out which one to call based on the type for out.

This code then demonstrates calling PrintThing() on both a Fruit and an Animal.

The Practice

  1. Break It -- Create a new type and try passing it to PrintThing() but do not have a Printer interface for it. Remember, the only thing PrintThing() requires is that your type have a Print() function, so just don't have that and you should get an error.
  2. Change It -- Now make your new type correctly implement Print() so Printer work.
  3. Recreate It -- Take some time to describe this code to yourself and then recreate it, or if you have your own idea that can use this then make that from your own description.

Study Drills

  1. Study the Stringer interface and use that as well with these types.
  2. Try changing the Print() methods to take pointers to their types and see how that changes your code.
Previous Lesson Next Lesson

Register for Learn Go the Hard Way

Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.