12.8 Returning Concrete Types vs Interfaces: API Design Guidance

Right, let’s settle this. One of the most common, and frankly, most tedious debates in Go API design is whether to return concrete types (*MyStruct) or interfaces (MyInterface). You’ll find zealots on both sides, but the correct answer, as with most things in engineering, is a deeply unsatisfying “it depends.” But I’ll give you the tools to know what it depends on. The core principle is this: Your function’s return type is a contract, a promise. The narrower the promise, the more freedom you have to change your implementation later without breaking the world.

12.7 Designing Small Interfaces: The io.Reader and io.Writer Lesson

Right, let’s talk about one of the most quietly brilliant design decisions in Go: the io.Reader and io.Writer interfaces. If you take only one thing from this book, let it be this: design your interfaces to be this small and focused. The standard library gods have handed us the perfect blueprint, and we’d be fools to ignore it. The genius is in their staggering simplicity. Here they are in their entirety:

12.6 Nil Interface vs Interface Holding a Nil Pointer: A Subtle Bug

Right, so you’ve got your interfaces working, you’re feeling good about your code, and then BAM. Your program does nothing. Or worse, it panics. You check your logic a hundred times and it’s flawless. The culprit? It’s probably this little nightmare: the difference between a nil interface and an interface holding a nil pointer. It’s the kind of subtle bug that makes you want to have a stern word with the language designers, but I promise there’s a method to this madness.

12.5 Interface Composition: Embedding Interfaces in Interfaces

Right, so you’ve got the hang of defining a single interface. Neat. But the real world, as usual, is messier. You’ll often find that what you actually need is a combination of behaviors. You could just define one giant SuperDuperWriterCloserLogger interface, but that’s brittle, inflexible, and frankly, it reeks of a committee-designed Java library from 2003. We’re better than that. Go’s answer is interface composition. It’s the idea that you can build complex interfaces by embedding smaller, focused ones inside them. It’s like building with Lego bricks instead of carving a monolith out of a single piece of rock. This is one of the most elegant features of the language, and it’s why you’ll see interfaces like io.ReadWriter all over the standard library. Let’s break it down.

12.4 The Empty Interface any (and interface{}): The Universal Type

Alright, let’s talk about the any type, or as it was known in its more verbose youth, interface{}. This is Go’s universal type, the linguistic equivalent of a cardboard box you use when you move house. You can shove absolutely anything in there—your fine china, your collection of novelty mugs, that weird statue your aunt gave you—but once it’s in the box, you lose all information about what it is. You just know it’s something.

12.3 Interface Values: Dynamic Type and Dynamic Value

Right, let’s get our hands dirty with what an interface value actually is under the hood. This is where the magic happens, and where most of the confusion comes from. It’s also where you’ll stop being afraid of them and start wielding them like a pro. Think of an interface variable not as a thing itself, but as a container, a pair of glasses. It has two components, and you must understand both to see the whole picture:

12.2 Implicit Implementation: No implements Keyword

Right, so you’ve seen interfaces before. You declare one, you explicitly state that your new struct implements that interface, you pat yourself on the back for writing good, clean, object-oriented code. Go toss that implements keyword in the bin. We don’t do that here. Go’s approach is different. It’s implicit. A type satisfies an interface simply by implementing the interface’s method set. No ceremony, no declaration of intent. If it has the methods, it is the interface. This is sometimes called “structural typing” or “duck typing” – if it quacks like a Duck, it’s a Duck, and we don’t need to see its birth certificate.

12.1 Interface Types: Named Sets of Method Signatures

Right, let’s talk about interface types. Forget the intimidating jargon for a second. An interface is, at its heart, a contract. It’s a named set of method signatures—a promise that a certain type will have these specific methods with these specific inputs and outputs. It doesn’t care about the state (the struct fields), it doesn’t care about the implementation details (how you get the job done), it only cares about behavior (what you can do).

— joke —

...