ProgrammingBackend Developer

How does nesting anonymous structures (struct embedding) work in Go and how does embedding differ from a regular struct field?

Pass interviews with Hintsage AI assistant

Answer.

Background

In the Go language, composition support is implemented through the embedding mechanism — the ability to include one structure inside another without an explicit field name. This approach was intended to allow modeling inheritance "in its own way", while maintaining the simplicity of the language and avoiding many complications associated with multiple inheritance.

Problem

Developers often want to extend the behavior or interface of a certain base structure without complicating the architecture. In Go, this is often achieved using embedding, which allows accessing methods and fields of the embedded type directly, as if they are defined in the parent structure. However, embedding has its peculiarities, and a misunderstanding of the mechanism can lead to unexpected errors, such as name conflicts, double embedding, and incorrect method inheritance.

Solution

Proper and economical use of embedding allows for a cleaner composition. It should be noted that methods and fields are "lifted" only one level up, and that embedding implements a "has-a" rather than an "is-a" relationship. One should avoid name conflicts and understand how the method receiver works.

Example code:

package main import "fmt" type Engine struct { Power int } func (e Engine) Start() { fmt.Println("Engine started with power", e.Power) } type Car struct { Engine // embedding, not a field Engine as engine Engine Brand string } func main() { c := Car{Engine: Engine{Power: 200}, Brand: "Toyota"} c.Start() // directly accessible fmt.Println(c.Power) // field is also lifted }

Key features:

  • Methods and fields of the embedded type are "lifted" to the top
  • Embedding allows implementing an interface if the embedded type implements the required methods
  • This is not "is-a" inheritance, but "has-a" composition

Trick questions.

Can embedding implement multiple inheritance?

No, embedding does not implement inheritance as in OOP; it is composition. You can embed several other structures into one, but upon method conflicts, it results in an assembly conflict, not merging.

What happens if the fields of the embedded structures have the same names?

There will be a compilation error upon direct access: the compiler does not know which field to refer to. The path must be explicitly specified through the name of the embedded structure.

Example code:

type A struct {X int} type B struct {X int} type C struct { A B } func main() { c := C{} // c.X = 1 // error: ambiguous selector c.A.X = 1 // only this way }

Are methods with value/pointer receivers lifted equally during embedding?

No. Methods with pointer receivers are lifted only if the structure also uses a pointer (c := &Car{}). If the struct is by value, methods with pointer receivers will not be "lifted".

Common mistakes and anti-patterns

  • Accidental "shadowing" of methods and fields from the embedded structure
  • Using embedding to imitate inheritance, violating the "has-a" concept
  • Violating the principle of clarity in composition

Real-life example

Negative case

A project with deeply nested structures where embedding is used to imitate multi-level inheritance. A newcomer does not understand from which structure a field or method comes, complicating the entire project and making it "magical".

Pros:

  • Quick assembly of behavior

Cons:

  • Poor readability, challenging maintenance, conflicts during structure refactoring

Positive case

Embedding is used to implement an interface; for example, the Logger interface is implemented through embedding a type with a Println method, explicitly documented and covered by tests.

Pros:

  • Simplicity of composition, minimal code template
  • Predictable lifting of methods and fields

Cons:

  • Conflicts can still arise when extending the interface, requiring careful architecture