Structs, Embedding & Field Promotion
Structs group named fields into a single type.
Search across all documentation pages
Structs group named fields into a single type.
Embedding lets you compose behavior and fields from other types without inheritance, promoting exported members to the outer type.
A struct literal sets fields by name or position (if unambiguous).
Embedded fields must be unnamed and their type name becomes a field name for promotion.
Promoted fields and methods are callable on the outer value as if declared there, unless shadowed.
Quick-reference recipe card - copy-paste ready.
package main
import "fmt"
type Logger struct{}
func (Logger) Log(msg string) { fmt.Println(msg) }
type Server struct {
Logger
Addr string
}
func main() {
s := Server{Addr: ":8080"}
s.Log("listening") // promoted method
fmt.Println(s.Addr)
}When to reach for this:
User, Order, Config).io.Reader, sync.Mutex).package main
import "fmt"
type Person struct {
Name string
Age int
}
type Employee struct {
Person
ID int
Dept string
}
func (p Person) Greet() string {
return "hello, " + p.Name
}
func describe(e Employee) {
fmt.Println(e.Name, e.ID, e.Greet())
}
func main() {
e := Employee{
Person: Person{Name: "Ada", Age: 36},
ID: 1001,
Dept: "research",
}
describe(e)
// Explicit outer field wins over promoted if names collide
type Shadow struct {
Person
Name string
}
sh := Shadow{Person: Person{Name: "Grace"}, Name: "Override"}
fmt.Println(sh.Name, sh.Person.Name)
}What this demonstrates:
Person to Employee.e.Name accesses embedded Person.Name.| Form | Example | When |
|---|---|---|
| Named fields | Person{Name: "Ada"} | Default - clear and order-free |
| Positional | Person{"Ada", 36} | Small stable structs only |
| Pointer literal | &Person{Name: "Ada"} | APIs expecting *Person |
| Partial | Person{Name: "Ada"} | Other fields zero |
| Situation | Result |
|---|---|
Single embedded T | outer.Field if T.Field exported |
Method on T | Callable as outer.Method() |
| Name collision on outer | Outer field shadows; use outer.T.Field |
| Multiple embeddings conflict | Ambiguous access - compiler error without qualifier |
// Embed interface to forward behavior
type ReadCloser struct {
io.Reader
io.Closer
}
// Embed mutex - zero value usable
type SafeMap struct {
sync.Mutex
m map[string]int
}*T with nil panics on promoted method call. Fix: ensure initialization or guard calls.==; compare fields manually.Name applies. Fix: avoid duplicate field names across outer and embedded types.| Alternative | Use When | Don't Use When |
|---|---|---|
Named field person Person | Clear ownership, no promotion | You want promoted methods |
| Embedding | True is-a composition (Server has logging) | Simple data grouping only |
| Interfaces | Behavior abstraction | You only need data fields |
| Separate packages | Hide embedded implementation | Tight local helpers |
Exported fields and methods of an embedded type appear on the outer type without a qualifier.
e.Name works when Person is embedded and has Name.
Yes, if promoted names do not conflict.
Ambiguous promotions require explicit selectors like e.Person.Name.
The embedded field is a real field stored inside the outer struct by value (or pointer if *T).
It is not a reference link.
If T has func (t *T) M(), outer O with embedded T gets M when O or *O is addressable per receiver rules.
You can embed only named types, not bare int.
Define type UserID int and embed UserID if needed.
Anonymous struct fields marshal inline (fields promoted to top level) unless tagged json:",inline" changes behavior for nested structs.
Named fields nest normally.
Zero-size marker type used for set keys map[K]struct{} and signaling channels.
It allocates no additional bytes for the value.
Tags are string metadata read via reflection (reflect.StructTag).
Packages like encoding/json and ORMs interpret them at runtime.
Only if all fields are comparable.
Slices, maps, and functions inside break comparability.
Assignment copies value types field-wise.
For pointer fields, copy duplicates pointers - deep copy manually if needed.
Use *T when mutating, avoiding copy cost for large structs, or representing optional presence.
Small immutable records can stay values.
Embedding unexportedType promotes only to types in the same package.
External packages cannot access promoted unexported members.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026