In Go, an interface is a type that defines a set of method signatures. A type satisfies an interface by implementing all of its methods.
Interfaces enable flexible code. Functions can operate on any type that satisfies the required methods. A type may satisfy multiple interfaces, and an interface may be satisfied by many types.
Define:
type shape interface { area() float64 perimeter() float64}
Here, the shape interface defines two method signatures: area() and perimeter(). Any type that implements both methods satisfies shape.
Both circle and rect satisfy shape by implementing all required methods. This happens automatically, no explicit declaration is needed.
Interface satisfaction also depends on method receivers. Methods with value receivers belong to both T and *T. Methods with pointer receivers belong only to *T.
type speaker interface { sayHello()}type person struct { name string}func (d *person) sayHello() { fmt.Println("Hello, my name is " + d.name)}func main() { var s speaker p := person{name: "John"} // s = p // <-- compiler: cannot use p (variable of struct type person) as speaker value in assignment: person does not implement speaker (method sayHello has pointer receiver) s = &p s.sayHello() // Hello, my name is John}
Functions can accept interface types as parameters, which lets them work with any type that satisfies the interface.
Examples:
Sorting a slice using Sort function ↗ from the sort package.
The Sort function accepts data that implements an interface. To pass the slice to it, we need to satisfy that interface ↗. It requires us to have Len() int, Less(i, j int) bool, and Swap(i, j int) methods.
The fmt package checks whether a value satisfies the Stringer interface ↗. This interface requires a single method, String() string. If a type implements that method, fmt uses it automatically when printing. Otherwise, it falls back to the default representation.
import ( "fmt")type ipAddr [4]bytefunc (i ipAddr) String() string { return fmt.Sprintf("is: %d.%d.%d.%d", i[0], i[1], i[2], i[3]) // prints the address as a dotted quad}func main() { hosts := map[string]ipAddr{ "loopback": {127, 0, 0, 1}, "googleDNS": {8, 8, 8, 8}, } for name, ip := range hosts { fmt.Println(name, ip) }}// loopback is: 127.0.0.1// googleDNS is: 8.8.8.8
As you can see from the output, fmt prints the map keys using the default behavior because they are plain string values. For the map values, it detects that they are of type IPAddr and automatically uses the custom String() method.
Customize printing errors.
Go programs express error state with error values. The built-in error type is an interface and defined like:
type error interface { Error() string}
If a type implements the Error() string method, it satisfies the error interface. The fmt package also looks for the error interface when printing values, so if a value is an error, fmt uses its Error() method.
import ( "fmt")type errNegativeVal intfunc (e errNegativeVal) Error() string { return fmt.Sprintf("Number can't be negative: %v", int(e))}func twoTimes(x int) (int, error) { if x < 0 { return 0, errNegativeVal(x) } return x * 2, nil}func main() { fmt.Println(twoTimes(4)) // 8 <nil> fmt.Println(twoTimes(2)) // 4 <nil> fmt.Println(twoTimes(-2)) // 0 Number can't be negative: -2}
A type assertion compares an interface value with a specific type and returns the underlying value if it matches.
Syntax:
val, ok := x.(assertedType)
assertedType: Can be any type, including named types and pointer types.
val: The extracted value. If the assertion fails, it is the zero value of the asserted type.
ok: A boolean indicating whether the assertion succeeded.
func main() { var x interface{} = "Hello" val, ok := x.(string) if ok { fmt.Println("It's a string:", val) } else { fmt.Println("Not a string") }}// It's a string: Hello
Here is an example showing usage with the shape interface defined above:
// ...func identifyShape(s shape) { val, ok := s.(circle) if ok { fmt.Println("Shape is a circle, val:", val) } else { fmt.Println("Shape is not a circle") // val is zero value here }}func main() { // ... identifyShape(myCircle) // Shape is a circle, val: {3}}
A type switch compares an interface value against multiple types.
func describe(i interface{}) { switch v := i.(type) { case string: fmt.Println("A string", v) case int: fmt.Println("An int", v) default: fmt.Println("Unknown type", v) }}func main() { x := 5 describe(x) // An int 5}
Type switches also work with named types and pointer types, not just basic types.
type user struct { name string}func describe(i interface{}) { switch v := i.(type) { case *user: fmt.Println("Pointer to User", v.name) case user: fmt.Println("User value", v.name) default: fmt.Println("Unknown type", v) }}func main() { u := user{name: "John"} describe(u) // User value John describe(&u) // Pointer to user John}
Examples:
var m = map[string]interface{}{ "Name": "Wednesday", "Age": 6, "Parents": []interface{}{ "Gomez", "Morticia", },}func main() { for k, v := range m { switch vv := v.(type) { case string: fmt.Println(k, "is string", vv) case float64: fmt.Println(k, "is float64", vv) case []interface{}: fmt.Println(k, "is an array:") for i, u := range vv { fmt.Println(i, u) } default: fmt.Println(k, "is of a type I don't know how to handle") } }}// Name is string Wednesday// Age is of a type I don't know how to handle// Parents is an array:// 0 Gomez// 1 Morticia