In Go, an interface is a type that defines a set of method signatures. A type satisfies an interface when it implements all of the methods declared by that interface.
Interfaces help you write flexible code by allowing functions to work with any type that provides the required methods. A type can satisfy multiple interfaces, and a single interface can be satisfied by many different 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 the shape interface because they implement all required methods. This happens automatically, with no explicit declaration.
Interface satisfaction also depends on method receivers. Methods defined on a value receiver are part of both T and *T, while methods defined on a pointer receiver are part of only *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:
Let's say you have a slice and want to sort it using the 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 is an operation that compares an interface value against 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
You can also omit ok, but if the assertion fails, it causes a panic:
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}}