Embedding is what’s done to diamonds π when they are put in a ring π You take something and place it deeply within something else. So that it becomes a part of it. Maybe you’ve embedded your phone case to your phone? π± I know I have, the thing’s impossible to get off!
Setup
Let’s make our directory embed
and the files we want inside of
that directory example_test.go
embed.go
mkdir embed
touch embed/example_test.go embed/embed.go
Now let’s open up embed.go
and for the very first line we’ll add
package embed
Next for example_test.go
for the very first line we’ll add
package embed_test
We can import basics/embed
into cmd/main.go
and run functions
from there with go run cmd/main.go
and also to run our example_test.go
π
we use go test embed/example_test.go
in the commandline.
Embedding Structs
The code does a good job of showing how to embed, but not why, so let’s understand why we would embed in the first place.
There are times when you want to add data to fields but don’t want to touch the
original struct
. Well in this case you can just wrap π― that struct
and put
new information with it’s own methods without needing to rewrite any code π₯Ή
this is such a common strategy in software engineering it has a name
Decorator pattern
.
Embedding allows for reusable code that can be composed into a larger struct
.
Coding Time!
embed.go
// Gopher is an example struct to show off embedding
type Gopher struct {
Name string
Age int
IsCoding bool
privateField string
}
// GopherV2 has a Gopher embedded inside of it and has some additional fields.
type GopherV2 struct {
// We can make this private if we want like:
// `gopher Gopher`
// but that would remove the inner type promotion, meaning we can't do fun
// and easy stuff like `myGopherV2.Name` π, we would have to do
// `myGopherV2.gopher.Name` π
Gopher
Friends []string
Ratings map[string]int
}
// GopherV3 has a GopherV2 which has a Gopher in it, so it is allowed to access
// all the fields and methods of both GopherV2 and Gopher along with it's own
// fields and methods.
type GopherV3 struct {
GopherV2
Badge rune
}
// Struct is an example function that shows how to initialize both
// versions of embedded structs and how to access the embedded fields.
func Struct() {
gala := GopherV2{
Gopher: Gopher{
Name: "Gala",
Age: 24,
IsCoding: false,
privateField: "Embedding doesn't change access modifiers"},
Friends: []string{"Gabby", "Gael", "Garth", "Gazsi"},
Ratings: map[string]int{"coffee": 3, "tea": 7, "chocolate": 9},
}
fmt.Printf("GopherV2: %#v\n", gala)
fmt.Println()
// Change our GopherV2
gala.Name = "gopher"
gala.Age = 26
gala.IsCoding = true
gala.privateField = "Can be changed because in same package."
gala.Friends = append(gala.Friends, "Gandalf")
gala.Ratings["garlic bread"] = 10
fmt.Printf("changed GopherV2: %#v\n", gala)
// This is here to show that you **always** need to have `field: field{}`
// when making your struct inline, but don't need to when accessing fields
// e.g. `g.name` works! Instead of `g.GopherV2.Gopher.name`
g := GopherV3{
GopherV2: GopherV2{
Gopher: Gopher{
Name: "Ground",
Age: 0xdead,
IsCoding: true,
privateField: "Access granted"},
Friends: []string{"Gunter"},
Ratings: map[string]int{"embedding": 10},
},
Badge: 'π€―',
}
fmt.Printf("Three layers of embedding %+v\n", g)
}
example_test.go
func ExampleStruct() {
embed.Struct()
// Output:
// GopherV2: embed.GopherV2{Gopher:embed.Gopher{Name:"Gala", Age:24, IsCoding:false, privateField:"Embedding doesn't change access modifiers"}, Friends:[]string{"Gabby", "Gael", "Garth", "Gazsi"}, Ratings:map[string]int{"chocolate":9, "coffee":3, "tea":7}}
//
// changed GopherV2: embed.GopherV2{Gopher:embed.Gopher{Name:"gopher", Age:26, IsCoding:true, privateField:"Can be changed because in same package."}, Friends:[]string{"Gabby", "Gael", "Garth", "Gazsi", "Gandalf"}, Ratings:map[string]int{"chocolate":9, "coffee":3, "garlic bread":10, "tea":7}}
// Three layers of embedding {GopherV2:{Gopher:{Name:Ground Age:57005 IsCoding:true privateField:Access granted} Friends:[Gunter] Ratings:map[embedding:10]} Badge:129327}
}
Embedding Has No Side Effects
This may be the first time you’ve ever seen embedding done and you may have the question
When I embed a
struct
does that change anything about either of those structs? π€
It’s a good question to have. π π And the answer is nothing unexpected will
happen when you put a struct
into another struct
. You get all the benefits
of being able to rely on inner type promotion and your struct
is as you
would expect it to behave.
Let’s dig a little more into that phrase “inner type promotion”. When you embed
a struct
the fields of that struct
are promoted to the encompassing
struct
. For example let’s have a bike in a moped in a motorcycle. π² -> π΅ ->
ποΈ All bikes have wheels, but we don’t need to say
motorcycle.moped.bike.wheels
We can just say what would make sense,
motorcycle.wheels
Likewise a moped can have a light on it π¦ but we don’t
need to say motorcycle.moped.light
It’s obvious that our motorcycle would be
a direct upgrade from the moped so we can promote that field light
π¦ up to
the motorcycle.
Coding Time!
embed.go
// City is a struct showing structs with embedded fields doesn't change
// anything about the way we expect a struct to behave
type City struct{ Residents []GopherV3 }
// Deep shows that structs with embedded fields act no differently with
// the embedded fields.
func Deep() {
c := City{
Residents: []GopherV3{
{ // π Notice we don't need to put GopherV3 here
GopherV2: GopherV2{
Gopher: Gopher{
Name: "Gance",
Age: 0b001010,
IsCoding: true,
privateField: "Not accessible outside package."},
Friends: []string{"Guzz"},
Ratings: map[string]int{"space": 10},
},
Badge: 'π',
},
{ // π Notice we don't need to put GopherV3 here
GopherV2: GopherV2{
Gopher: Gopher{
Name: "Guuba",
Age: 0o777,
IsCoding: false,
privateField: "Can be accessed in package."},
Friends: []string{"Ghorm", "Gokil"},
Ratings: map[string]int{"death metal": 10},
},
Badge: 'π',
},
{ // π Notice we don't need to put GopherV3 here
GopherV2: GopherV2{
Gopher: Gopher{
Name: "Gerry",
Age: 88,
IsCoding: true,
privateField: "Put stuff here"},
Friends: []string{"Gaqlyn", "Gicard", "Gosemary"},
Ratings: map[string]int{"naps": 10},
},
Badge: 'π',
},
{ // π Notice we don't need to put GopherV3 here
GopherV2: GopherV2{
Gopher: Gopher{
Name: "Gustion",
Age: 21,
IsCoding: true,
privateField: "that you need to use"},
Friends: []string{"Gidea", "Gno"},
Ratings: map[string]int{"carbs": 10},
},
Badge: 'π’',
},
{ // π Notice we don't need to put GopherV3 here
GopherV2: GopherV2{
Gopher: Gopher{
Name: "Guna",
Age: 0b11111111,
IsCoding: false,
privateField: "but other packages don't."},
Friends: []string{"Gouda"},
Ratings: map[string]int{"haircuts": 3},
},
Badge: 'π',
},
},
}
fmt.Printf("%+v\n\n", c)
for _, g := range c.Residents {
// g.GopherV2.Gopher.privateField
fmt.Printf("%s ", g.privateField)
}
}
example_test.go
func ExampleDeep() {
embed.Deep()
// Output:
// {Residents:[{GopherV2:{Gopher:{Name:Gance Age:10 IsCoding:true privateField:Not accessible outside package.} Friends:[Guzz] Ratings:map[space:10]} Badge:128640} {GopherV2:{Gopher:{Name:Guuba Age:511 IsCoding:false privateField:Can be accessed in package.} Friends:[Ghorm Gokil] Ratings:map[death metal:10]} Badge:127755} {GopherV2:{Gopher:{Name:Gerry Age:88 IsCoding:true privateField:Put stuff here} Friends:[Gaqlyn Gicard Gosemary] Ratings:map[naps:10]} Badge:127881} {GopherV2:{Gopher:{Name:Gustion Age:21 IsCoding:true privateField:that you need to use} Friends:[Gidea Gno] Ratings:map[carbs:10]} Badge:127842} {GopherV2:{Gopher:{Name:Guna Age:255 IsCoding:false privateField:but other packages don't.} Friends:[Gouda] Ratings:map[haircuts:3]} Badge:127772}]}
//
// Not accessible outside package. Can be accessed in package. Put stuff here that you need to use but other packages don't.
}
Embedding For Interface
This is where embedding gets really powerful. That’s because you’ll find
yourself needing to satisfy an interface
from time to time in the standard
library or from other libraries in general. Maybe you don’t want to create the
needed methods yourself. Well if you embed a struct
that already satisfies
that interface
, you now satisfy that interface
π It’s by far one of the
coolest π§ things that comes with Go’s composition and embedding features.
Need an io.Reader
? Three separate ways to satisfy the interface
type MyStructV1 struct{ io.Reader }
type MyStructV2 struct{ strings.Reader }
type MyStructV3 struct{ *os.File }
These aren’t meant to be useful examples but open your eyes π to the ability to compose structs into exactly what you need very quickly with smaller pre-built components.
Coding Time!
embed.go
type Human interface{ Greet() string }
type Bear interface{ Growl() string }
type Pig interface{ MudBath(minutes int) }
// HumanBearPig is the thing of nightmares.
type HumanBearPig interface {
Human // half human
Bear // half bear
Pig // half pig
}
func (g Gopher) Greet() string { return "Hi-dilly-ho, neighborinos!" }
func (g Gopher) Growl() string { return "Rawr XD" }
func (g Gopher) MudBath(m int) { fmt.Printf("Rolled around for %d mins", m) }
func (g GopherV2) Growl() string { return "GOPHERV2 GRRRWAUGH!!" }
func (g GopherV2) MudBath(m int) { fmt.Printf("V2:Sat in mud for %d mins", m) }
func (g GopherV3) Greet() string { return "GOPHERV3 SAYS WHADDUP!!" }
// Interface shows that we need a struct that will satisfy the entire
// interface, but we don't care if that struct or the embedded structs inside
// of it satisfy the interface.
func Interface(hbp HumanBearPig) {
fmt.Println("It was a dark and rainy night. The moon π felt so bright.")
fmt.Println("A strange passerby. Avert my gaze, I try.")
fmt.Println(hbp.Greet())
fmt.Println("The person yelled to me; suddenly something broke free.")
fmt.Println(hbp.Growl())
fmt.Println("I ran as fast I could, through the streets to the forests of wood.")
fmt.Println("A final leap over some mud. The thing ceased with a great thud.")
hbp.MudBath(20)
}
example_test.go
func ExampleInterface() {
embed.Interface(embed.Gopher{})
// Output:
// It was a dark and rainy night. The moon π felt so bright.
// A strange passerby. Avert my gaze, I try.
// Hi-dilly-ho, neighborinos!
// The person yelled to me; suddenly something broke free.
// Rawr XD
// I ran as fast I could, through the streets to the forests of wood.
// A final leap over some mud. The thing ceased with a great thud.
// Rolled around for 20 mins
}
func ExampleInterface_v2() {
embed.Interface(embed.GopherV2{})
// Output:
// It was a dark and rainy night. The moon π felt so bright.
// A strange passerby. Avert my gaze, I try.
// Hi-dilly-ho, neighborinos!
// The person yelled to me; suddenly something broke free.
// GOPHERV2 GRRRWAUGH!!
// I ran as fast I could, through the streets to the forests of wood.
// A final leap over some mud. The thing ceased with a great thud.
// V2:Sat in mud for 20 mins
}
func ExampleInterface_v3() {
embed.Interface(embed.GopherV3{})
// Output:
// It was a dark and rainy night. The moon π felt so bright.
// A strange passerby. Avert my gaze, I try.
// GOPHERV3 SAYS WHADDUP!!
// The person yelled to me; suddenly something broke free.
// GOPHERV2 GRRRWAUGH!!
// I ran as fast I could, through the streets to the forests of wood.
// A final leap over some mud. The thing ceased with a great thud.
// V2:Sat in mud for 20 mins
}