Embedding
In Go, embedding is a mechanism that allows you to include one type (usually a struct) within another struct or interface without explicitly declaring fields or methods. Unlike traditional inheritance, embedding in Go does not imply an “is a” relationship but rather a “has a” or “contains” relationship. This design offers code reuse, method promotion, and simpler composition patterns.
Below is an in-depth look at how embedding works in Go, with several examples demonstrating its usage and behavior.
1. Struct Embedding
1.1 Basic Struct Embedding
When you embed one struct (say Embedded
) inside another struct (say Outer
), all exported fields and methods of Embedded
become “promoted” and accessible via Outer
.
Explanation:
Employee
embedsPerson
.We can access
Name
andAge
directly onEmployee
because they are promoted fields from the embeddedPerson
.Embedding is not inheritance; it's more like
Employee
has aPerson
.
Output:
1.2 Methods Promotion
When you embed a struct, all its exported methods are also promoted. You can call these methods on the embedding struct directly.
Explanation:
Greet()
is defined onPerson
, but by embeddingPerson
inEmployee
,Employee
also hasGreet()
.This is called method promotion.
Output:
2. Pointer vs Value Receivers and Embedding
If you have a method that uses a pointer receiver in the embedded type, you can call it through the embedding struct whether or not the embedding struct is a value or a pointer. Go automatically adjusts the receiver appropriately.
Explanation:
(*Person).SetName
is a pointer-receiver method.When called on
e
, which is a value, Go automatically uses&e.Person
.This makes it easier to call methods on embedded fields without worrying about pointer vs value issues (within reason).
Output:
3. Overlapping or Shadowing Fields
If both the embedded type and the embedding type have fields of the same name, the embedding type’s field takes precedence, and you can still access the embedded type’s field with a qualified name.
Explanation:
Both
Employee
andPerson
have a field namedName
.The direct
e.Name
refers toEmployee
’sName
.To access the
Person
field, we must fully qualify:e.Person.Name
.
Output:
4. Nested Embedding
You can embed multiple levels of structs. If a struct is embedded in an embedded struct, fields and methods continue to promote upward as long as there are no conflicts.
Explanation:
Employee
embedsPerson
, which in turn embedsAddress
.So,
Employee
has promoted fields from bothPerson
andAddress
, such asName
,City
, andState
.
Output:
5. Embedding in Interfaces
Go also allows embedding interfaces within other interfaces. This gives a form of “interface composition” rather than inheritance.
Example: Composing Interfaces
Explanation:
WriteCloser
is an interface that embedsWriter
andCloser
.Any type that implements both
Write
andClose
automatically satisfiesWriteCloser
.This is not inheritance but rather a simpler way to combine interface contracts.
6. Embedded Unexported Fields
You can embed unexported fields too, often for implementing patterns like “private inheritance” or for providing shared functionality without exposing it to consumers.
Explanation:
internalData
is not exported (starts with a lowercasei
).PublicType
embedsinternalData
to store data internally without exposing it to callers.This pattern can help maintain encapsulation while using embedding for code reuse.
7. Summary of Key Points
Composition Over Inheritance:
Go uses embedding as a simpler mechanism for code reuse rather than traditional OOP inheritance.Promotion:
Fields and methods of the embedded type are “promoted” to the embedding type, if they do not conflict with existing fields or methods.Shadowing:
If the embedding type defines a field or method with the same name as an embedded field/method, the embedding type’s definition takes precedence, and the embedded one can be accessed via fully qualified syntax.Pointer vs Value:
Whether you embed a pointer to a struct or a struct value, method calls can still be promoted. However, embedding a pointer to a struct has different lifecycle and memory implications than embedding by value.Nested Embedding:
Embedding can be nested multiple levels deep. Fields continue to be promoted up the chain, as long as there are no name conflicts.Interface Embedding:
Interfaces can embed other interfaces, effectively extending the interface set without classical inheritance.
Conclusion
Embedding in Go is a powerful construct that supports clean composition patterns without the complexity of traditional class-based inheritance. By understanding how fields and methods are promoted, how shadowing works, and how nested embedding can simplify structures, you can create more maintainable and idiomatic Go programs. Whether embedding structs or interfaces, remember that it represents “has a” relationships rather than “is a.” This design principle aligns with Go’s emphasis on simplicity and composition, enabling expressive and clear code reuse.