Healy Inkorperated

Notes and Other Thoughts

Follow me on GitHub

Interfaces

An interface is just a type that defines a set of methods. Implementing an interface is a simple as implementing all of its methods. A variable of interface type can store a value of any type with a method set that is a superset of that interface.

Quick Facts

  • Interfaces are usually named [type]er
  • The zero-value of an interface is nil

Method Sets

The method set of an interface type is its interface. For any other type T, the method set consists of all methods that are defined with receiver type T. Pointers expand this. For example, the method set of type *T is the set of all methods defined with receiver type T or *T. Because of this, the method set for *T contains the members of the method set for T.

Example:

type Dog struct {
	stomach []string
}

func (d *Dog) Eat(food string) {
	d.stomach = append(d.stomach, food)
}

type Cow struct {
	stomachs [4][]string // Cows have 4 stomachs. Google it
}

func (c *Cow) Eat(food string) {
	c.stomachs[0] = append(c.stomachs[0], food)
	// The cow is a mysterious creature, and it will magically pass
	//	food from one stomach to the next. We will not implement this.
}

type Eater interface {
	Eat(string)
}

func FeedCorn(e Eater) {
	e.Eat("corn")
}

func main() {
	d := Dog{}
	c := Cow{}

	d.Eat("spaghetti") // { [spaghetti] }
	c.Eat("sandwich") // { [ [sandwich] [] [] [] ] }

	var e Eater
	e = &d // make d an Eater, since it has an Eat func
	e.Eat("kale")

	e = &c
	e.Eat("spinach")

	FeedCorn(&d) // { [spaghetti, kale, corn] }
	FeedCorn(&c) // { [sandwich, spinach, corn] [] [] [] }
	FeedCorn(e) // { [sandwich, spinach, corn, corn] [] [] [] }
}

Implementing External Interfaces

Unlike methods, interfaces can be implemented outside of the package in which they are defined. The only caveat is that you must import the package in which it is defined, as should seem obvious.

Example Interfaces

// in "fmt"
type Stringer interface {
	String() string
}

// in "sort"
type Interface interface {
	Len() int
	Less(i, j int) bool
	Swap(i, j int)
}

Usage

package main
import (
	"fmt"
	"sort"
)

type Pony struct {
	Name string
	Height, Weight float64
	Foods []string
}


func (p Pony) String() string {
	return fmt.Sprintf("%s(%f)", p.Name, p.Weight)
}

type Ponies []Pony

func (ps Ponies) Len() int {
	return len(ps)
}

func (ps Ponies) Less(i, j int) bool {
	return ps[i].weight < ps[j].weight
}

func (ps Ponies) Swap(i, j int) {
	ps[i], ps[j] = ps[j], ps[i]
}

func main() {
	ranch := make(Ponies, 100)
	for i := range ranch  {
		ranch[i].Name = fmt.Sprintf["p%d", i]
		ranch[i].Weight = float64(i%3)
	}
	fmt.Println(ranch)
	// [p0(0) p1(1) p2(2) p3(0) ...]
	sort.Stable(ranch) // works, since ranch implements sort.Interface

	fmt.Println(ranch)
	// [p0(0), p3(0), p6(0), ...]
}

Empty Interface

The empty interface is the interface type that specifies 0 methods. It looks like this:

interface{}

Every type implements the empty interface because every type has at least zero methods in its method set. Since every type implements the empty interface, we can store anything there, like void* in C++, but less weird.

func describe(thing interface{}) {
	fmt.Printf()"%v;%T\n", thing, thing)
}

func main() {
	var i interface{}
	fmt.Println(i)

	i = 5

	describe(i)
}

Casting Empty Interfaces back to Other types

var i interface{} = hello
s := i.(string)
fmt.Println(s) // hello

f := i.(float64) // panic
f, ok := i.(float64) // no panic, ok is false

This is alright, but would be tedious in practice. Enter the type switch.

Type Switches

A type switch takes an interface and matches cases based on its type

switch v := i.(type) { // i.(type) MUST be written like this
case int:
	// v is an int
case string:
	// v is a string
default:
	// v is an empty interface
}

Ahh, much better.