Interfaces
An interface is a set of method signatures. Any type whose method set includes them all satisfies the interface — automatically:
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct{}
type Robot struct{ id int }
func (d Dog) Speak() string { return "Woof!" }
func (r Robot) Speak() string { return fmt.Sprintf("BEEP-%d", r.id) }
func announce(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
announce(Dog{}) // Woof!
announce(Robot{42}) // BEEP-42
}
Notice what's missing: Dog never says implements Speaker. It has a
Speak() string method, so it is a Speaker. That's the whole mechanism.
Why implicit? Decoupling
In most languages, implementing an interface requires the type's author to know about the interface and declare the relationship. Go inverts this: the consumer declares what it needs, and any type that happens to fit, fits — even types written years earlier, in packages that have never heard of yours.
This is Go's decoupling story. You can define a two-method interface in your
package and instantly accept *os.File, bytes.Buffer, or a teammate's type,
without any of them changing. Interfaces describe behavior a caller
requires, not ancestry a type claims.
Small interfaces win
Because satisfaction is free, idiomatic Go interfaces are tiny — often one method. The two most famous live in the standard library:
// io.Writer: anything you can write bytes to.
type Writer interface {
Write(p []byte) (n int, err error)
}
// fmt.Stringer: anything that can describe itself as a string.
type Stringer interface {
String() string
}
fmt.Fprintf takes an io.Writer, which is why the same call can print to a
file, a network connection, an HTTP response, or an in-memory buffer. And if
your type has a String() string method, every fmt verb like %v and
fmt.Sprint will use it automatically to format your value. The Go proverb:
"The bigger the interface, the weaker the abstraction."
Interface values: a type and a value
A variable of interface type holds two things: the concrete type stored in it and that type's value.
var s Speaker // nil interface: no type, no value
s = Dog{} // type: Dog, value: Dog{}
s = Robot{7} // type: Robot, value: Robot{7}
A call like s.Speak() uses the stored type to find the right method — that's
dynamic dispatch, decided at run time. When both parts are unset, the
interface is nil, and calling a method on it panics. Keep this two-part
picture in mind; it explains the type assertions of the next lesson and a
classic nil-error trap you'll meet in the errors chapter.
Try it
Give a Duration type a String() string method so it satisfies
fmt.Stringer — then watch fmt.Sprint pick it up without being told.
Practice
1. Implement fmt.Stringer
IntermediateThe starter defines a duration measured in whole minutes:
type Duration int // a length of time in minutesGive it a
Stringmethod so it satisfiesfmt.Stringer:func (d Duration) String() stringFormat rules:
- Under an hour: just minutes —
Duration(45)→"45m". - Exact hours: just hours —
Duration(180)→"3h". - Otherwise both —
Duration(135)→"2h15m". - Zero is
"0m".
You never register anything with the
fmtpackage: the moment your type has aString() stringmethod, it satisfiesfmt.Stringerimplicitly, and functions likefmt.Sprintand the%vverb call it for you. The tests check both routes — callingString()directly and passing aDurationthroughfmt.Sprint.Integer division and remainder (
d / 60,d % 60) do the splitting;fmt.Sprintfbuilds the string.package main import "fmt" // Duration is a length of time in whole minutes. type Duration int // String formats the duration as "2h15m", "45m" (under an hour), // or "3h" (exact hours). Zero is "0m". func (d Duration) String() string { // TODO: implement return "" } func main() { meeting := Duration(135) fmt.Println(meeting) // fmt calls String() for us: 2h15m }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- Under an hour: just minutes —