Consider the type ColoredPoint
:
import "image/color" type Point struct{ X, Y float64 } type ColoredPoint struct { Point Color color.RGBA }
We could have defined ColoredPoint
as a struct of three fields, but instead we
embedded a Point
to provide the X
and Y
fields. As we saw in
Section 4.4.3, embedding lets us take a syntactic shortcut to defining
a ColoredPoint
that contains all the fields of Point
, plus some
more. If we want, we can select the fields of ColoredPoint
that were
contributed by the embedded Point
without mentioning Point
:
var cp ColoredPoint cp.X = 1 fmt.Println(cp.Point.X) // "1" cp.Point.Y = 2 fmt.Println(cp.Y) // "2"
A similar mechanism applies to the methods of Point
. We can
call methods of the embedded Point
field using a receiver of type
ColoredPoint
, even though ColoredPoint
has no declared methods:
red := color.RGBA{255, 0, 0, 255} blue := color.RGBA{0, 0, 255, 255} var p = ColoredPoint{Point{1, 1}, red} var q = ColoredPoint{Point{5, 4}, blue} fmt.Println(p.Distance(q.Point)) // "5" p.ScaleBy(2) q.ScaleBy(2) fmt.Println(p.Distance(q.Point)) // "10"
The methods of Point
have been promoted to ColoredPoint
.
In this way, embedding allows complex types with many methods to be
built up by the composition of several fields, each providing a few
methods.
Readers familiar with class-based object-oriented languages may be
tempted to view Point
as a base class and ColoredPoint
as a subclass or derived class, or to interpret the relationship
between these types as if a ColoredPoint
“is a” Point
.
But that would be a mistake.
Notice the calls to Distance
above. Distance
has a
parameter of type Point
, and q
is not a Point
, so
although q
does have an embedded field of that type, we must explicitly
select it.
Attempting to pass q
would be an error:
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
A ColoredPoint
is not a Point
, but it “has a”
Point
, and it has two additional methods Distance
and
ScaleBy
promoted from Point
.
If you prefer to think in terms of implementation, the embedded field
instructs the compiler to generate additional wrapper methods that
delegate to the declared methods, equivalent to these:
func (p ColoredPoint) Distance(q Point) float64 { return p.Point.Distance(q) } func (p *ColoredPoint) ScaleBy(factor float64) { p.Point.ScaleBy(factor) }
When Point.Distance
is called by the first of these wrapper methods,
its receiver value is p.Point
, not p
, and there is no
way for the method to access the ColoredPoint
in which
the Point
is embedded.
The type of an anonymous field may be a pointer to a named
type, in which case fields and methods are promoted indirectly from
the pointed-to object.
Adding another level of indirection lets us share common structures
and vary the relationships between objects dynamically.
The declaration of ColoredPoint
below embeds a *Point
:
type ColoredPoint struct { *Point Color color.RGBA } p := ColoredPoint{&Point{1, 1}, red} q := ColoredPoint{&Point{5, 4}, blue} fmt.Println(p.Distance(*q.Point)) // "5" q.Point = p.Point // p and q now share the same Point p.ScaleBy(2) fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
A struct type may have more than one anonymous field. Had we declared
ColoredPoint
as
type ColoredPoint struct { Point color.RGBA }
then a value of this type would have all the methods of Point
,
all the methods of RGBA
, and any additional methods declared on
ColoredPoint
directly.
When the compiler resolves a selector such as p.ScaleBy
to a
method, it first looks for a directly declared method named
ScaleBy
, then for methods promoted once from
ColoredPoint
’s embedded fields, then for methods promoted twice
from embedded fields within Point
and RGBA
, and so on.
The compiler reports an error if the selector was ambiguous because
two methods were promoted from the same rank.
Methods can be declared only on named types (like Point
) and
pointers to them (*Point
), but thanks to embedding, it’s possible and sometimes useful
for unnamed struct types to have methods too.
Here’s a nice trick to illustrate. This example shows part of a simple cache implemented using two package-level variables, a mutex (§9.2) and the map that it guards:
var ( mu sync.Mutex // guards mapping mapping = make(map[string]string) ) func Lookup(key string) string { mu.Lock() v := mapping[key] mu.Unlock() return v }
The version below is functionally equivalent but groups together the
two related variables in a single package-level variable, cache
:
var cache = struct { sync.Mutex mapping map[string]string } { mapping: make(map[string]string), } func Lookup(key string) string { cache.Lock() v := cache.mapping[key] cache.Unlock() return v }
The new variable gives more expressive names to the variables related
to the cache, and because the sync.Mutex
field is embedded within
it, its Lock
and Unlock
methods are promoted to the unnamed
struct type, allowing us to lock the cache
with a
self-explanatory syntax.