Skip to main content
Go Interface Structure

Interfaces

Interfaces are named collections of method signatures. They are the core of polymorphism in Go.

Interface Definition

type Abser interface {
    Abs() float64
}

Implicit Implementation

A type implements an interface by implementing its methods. There is no explicit declaration of intent, no “implements” keyword.
type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat implements Abser
    a = &v // a *Vertex implements Abser

    // a = v // Error! Vertex does not implement Abser (Abs method has pointer receiver)

    fmt.Println(a.Abs())
}

Interface Internals

Under the hood, an interface value is represented as a pair: (type, value) or more precisely (itab, data) Where:
  • itab (interface table): Contains type information and a pointer to the method table for the concrete type.
  • data: Pointer to the actual value.
Go Interface Structure Dynamic Dispatch: When you call a method on an interface value, Go looks up the method in the itab and calls it on the data pointer. This is how polymorphism works in Go.
var a Abser = MyFloat(3.14)
// a.itab points to the method table for MyFloat
// a.data points to the value 3.14
a.Abs() // Go looks up Abs in MyFloat's method table and calls it

Interface Values with Nil Underlying Values

This is a common source of confusion. An interface can be in three states:
  1. Nil interface: Both type and value are nil (nil, nil)
  2. Non-nil interface with nil value: Type is set, value is nil (*T, nil)
  3. Non-nil interface with non-nil value: Both are set (*T, &value)
var i interface{}
fmt.Println(i == nil) // true: (nil, nil)

var p *int
i = p
fmt.Println(i == nil) // false! (*int, nil) - type is set
fmt.Println(i.(*int) == nil) // true - the underlying value is nil
Why This Matters: A method can be called on a nil interface value if the interface is non-nil (state 2). This is why Go methods often check for nil receivers:
func (v *Vertex) Abs() float64 {
    if v == nil {
        return 0
    }
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

The Empty Interface

The interface type that specifies zero methods is known as the empty interface:
interface{}
An empty interface may hold values of any type. (Every type implements at least zero methods.) Empty interfaces are used by code that handles values of unknown type. For example, fmt.Print takes any number of arguments of type interface{}.
func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i)
}

func main() {
    var i interface{}
    describe(i)

    i = 42
    describe(i)

    i = "hello"
    describe(i)
}

Type Assertions

A type assertion provides access to an interface value’s underlying concrete value.
t := i.(T)
This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t. If i does not hold a T, the statement will trigger a panic. To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.
t, ok := i.(T)
If i holds a T, then t will be the underlying value and ok will be true. If not, ok will be false and t will be the zero value of type T, and no panic occurs.
func main() {
    var i interface{} = "hello"

    s := i.(string)
    fmt.Println(s)

    s, ok := i.(string)
    fmt.Println(s, ok)

    f, ok := i.(float64)
    fmt.Println(f, ok) // 0 false

    // f = i.(float64) // panic
}

Type Switches

A type switch is a construct that permits several type assertions in series.
func do(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
        fmt.Printf("I don't know about type %T!\n", v)
    }
}