Interface type defines a set of method signatures.

type Abser interface {
	Abs() float64
}

A value of interface type can hold any value that implements those methods.

var a Abser

Interfaces are implemented implicitly, aka Duck typing. There’s no explicit declaration like implements keyword.

Interface value

Interface value under the hood can be thought of as a tuple of a value and a concrete type:

(value, type)

Calling a method on an interface value executes the method of the same name on its underlying type.

A nil interface value holds neither value nor concrete type. Calling a method on a nil interface will produce runtime error.


Calls with nil receiver

When the value inside the interface is nil, the method will be called with a nil receiver.

type I interface {
	M()
}
 
type T struct {
	S string
}
 
func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}
 
func main() {
	var i I
 
	var t *T
	i = t
	i.M() // output "<nil>"
}

In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver (as with the method M in this example.)

Note that an interface value that holds a nil concrete value is itself non-nil.

Empty interface

The interface type that specifies zero methods is known as the empty interface:

interface{}

Basically it’s any type, because all types 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 main() {
	var i interface{}
	describe(i) // output "(<nil>, <nil>)"
 
	i = 42
	describe(i) // output "(42, int)"
 
	i = "hello"
	describe(i) // output "(hello, string)"
}
 
func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

Type assertions

You can access an interface value’s underlying concrete value with:

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.

var i interface{} = "hello"
 
s := i.(string)
fmt.Println(s) // output "hello"
 
s, ok := i.(string)
fmt.Println(s, ok) // output "hello true"
 
f, ok := i.(float64)
fmt.Println(f, ok) // output "0 false"
 
f = i.(float64) // panic
fmt.Println(f)

Type switches

A type switch is a construct that permits several type assertions in series. It is like a regular switch statement, but the cases in a type switch specify types (not values).

switch v := i.(type) { // `type` is keyword, not placeholder
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}

The declaration in a type switch has the same syntax as a type assertion i.(T), but the specific type T is replaced with the keyword type.

Example:

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)
	}
}
 
func main() {
	do(21) // "Twice 21 is 42"
	do("hello") // ""hello" is 5 bytes long"
	do(true) // "I don't know about type bool!"
}