What does Go's io.ReadWriteCloser gain by embedding io.Reader, io.Writer and io.Closer?
answer
- three small names, one new interface
- no new method signatures are written
- the method set is a union
- no code is inherited, only requirements
- any type with all three qualifies
basics
~20 sEmbedding folds the other interfaces' method sets into the new one, so io.ReadWriteCloser requires exactly Read, Write and Close and declares nothing extra. Any type with all three methods satisfies it automatically, with no declaration of intent.
solid answer
~50 sAn interface body may list embedded interface names instead of method signatures, and the resulting method set is the union of them. `io.ReadWriteCloser` is declared as `interface { Reader; Writer; Closer }`, so it means "has `Read`, `Write` and `Close`" and adds nothing of its own. Satisfaction stays implicit: `*os.File` never mentions the interface, it just has the three methods. Because the method set is a superset, a value of type `io.ReadWriteCloser` can be passed anywhere an `io.Reader` is wanted. Embedding here is pure composition of requirements, not inheritance -- an interface holds no code, so nothing is inherited and there is no default implementation. It is the reason the standard library keeps interfaces at one method each: the small ones are the vocabulary, and the compound ones are spelled out of them where needed.
code
go · 18 linestype Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// ReadWriteCloser declares no method of its own.
type ReadWriteCloser interface {
Reader
Writer
Closer
}go deeper
Be ready to say that io.ReadWriteCloser is just Read plus Write plus Close, and that a type satisfies it simply by having those three methods with matching signatures.
An interviewer expects the mechanics: embedding unions method sets at compile time, adds no code, and makes the compound interface assignable to any of its parts but not the other way round.
Show the design consequence in review: prefer composing small interfaces at the call site over declaring one wide interface, so callers and test fakes only carry the methods that are actually used.
Own the vocabulary decision for a shared package. Small embeddable interfaces are cheap to extend by composition, whereas a wide compound interface exported early fixes an obligation on every downstream implementer.
## What an interface declaration may contain A Go interface type lists what a value must be able to do. Its body can hold two kinds of line: a **method signature**, and the **name of another interface**, which is called *embedding*. When you embed an interface, every method it requires becomes a requirement of the new interface too. The resulting method set is the union of the embedded sets plus any methods you spelled out directly. The `io` package is the canonical demonstration: - `io.Reader` requires `Read(p []byte) (n int, err error)` - `io.Writer` requires `Write(p []byte) (n int, err error)` - `io.Closer` requires `Close() error` - `io.ReadWriteCloser` embeds all three and declares nothing else So `io.ReadWriteCloser` is not a bigger idea than its parts; it is a *name* for the conjunction of them. `io.ReadWriter`, `io.ReadCloser` and `io.WriteCloser` exist in the same family for the other combinations. ## Satisfaction stays implicit Go has no `implements` keyword. A type satisfies an interface when its method set contains every required method with an identical signature -- the compiler checks that at the point of assignment or call, not at the point of declaration. `*os.File` has `Read`, `Write` and `Close` (among many others), so it satisfies `io.ReadWriteCloser` without the `os` package having heard of the interface. Your own type does exactly the same: write the three methods and you are done. A common beginner mistake is thinking the implementing struct must itself embed `io.Reader` -- it must not; embedding an interface into a *struct* is a different mechanism with different consequences. ## Embedding is not inheritance An interface contains no code at all -- only requirements. So embedding cannot hand an implementer a default `Read`, cannot provide a base implementation, and has no notion of overriding or of calling "up". Everything embedding does is arithmetic on method sets at compile time. This is why the compound interfaces cost nothing at run time: an interface value is still a pair of (dynamic type, value), and calling `Close` through `io.ReadWriteCloser` costs the same as calling it through `io.Closer`. ## Assignability runs from wide to narrow Because satisfaction is by method set, a value typed as `io.ReadWriteCloser` may be assigned to a variable of type `io.Reader`, or passed to `func Count(r io.Reader)`. The wider interface's method set is a superset of the narrower one's, so the requirement is met. The reverse does not work: an `io.Reader` cannot be assigned to an `io.ReadWriteCloser`, because the compiler has no evidence that `Write` and `Close` exist. Getting from narrow back to wide requires a run-time type assertion, which is a different topic. ## Duplicate methods across embedded interfaces Embedding two interfaces that both require a method with the same name is fine in current Go as long as the signatures are identical -- the union simply contains it once. If the signatures differ, the declaration is a compile-time error, because no single type could satisfy both. ## Why this shape matters for design The payoff is that the small interfaces are reusable and the compound ones are free. If `io` had started with one large `File` interface, every function would have demanded far more than it used, and every fake in a test would have had to implement the lot. Instead each function asks for the narrowest thing it actually calls: `io.Copy` takes an `io.Writer` and an `io.Reader`, so it works over files, network connections, `bytes.Buffer`, HTTP bodies and pipes alike, none of which were designed with `io.Copy` in mind. When you design your own interfaces, follow the same order: write the one- and two-method interfaces that describe single capabilities, and compose them by embedding only where a caller genuinely needs the combination. A three-line interface that embeds two others reads better than a six-method one, and each half stays independently usable. ## What to remember Embedding an interface inside an interface unions method sets, adds no implementation, is resolved entirely at compile time, and keeps the standard library's small interfaces composable.
- Can a value typed io.ReadWriteCloser be passed to a function that takes io.Reader?Yes. Assignability between interfaces is decided by method sets, and `io.ReadWriteCloser`'s set is a superset of `io.Reader`'s, so the compiler accepts it. The reverse fails: an `io.Reader` has no proven `Write` or `Close`, so widening needs a run-time type assertion instead.
- What happens if you embed two interfaces that both declare a method with the same name?Current Go accepts it as long as both signatures are identical -- the method appears once in the union. If the signatures differ, the interface declaration itself is a compile-time error, since no type could ever satisfy both requirements at once.
- Does embedding io.Reader give implementers a Read method for free?No. An interface holds requirements, never code, so embedding can only add obligations. Anything satisfying the compound interface must supply all three method bodies itself. That is the sharp difference from embedding a concrete type in a struct, where real methods are promoted.
It is like a job advert that says "must hold the three certificates listed below" rather than teaching you anything: it states requirements, it does not supply skills.
saying these in an interview costs you the question
- Says embedding gives implementers a default Read implementation
- Thinks the implementing struct must also embed io.Reader
- Believes a type must declare that it implements io.ReadWriteCloser
- Claims io.ReadWriteCloser adds methods beyond Read, Write and Close
- Calls interface embedding inheritance from a base interface