Method Promotion
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 36 of 107.
Just as struct embedding promotes fields to the outer struct, Go also promotes methods from embedded types. This means you can call methods defined on the embedded struct directly on the outer struct.
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // embedded
}
Since Car embeds Engine, the Start() method is automatically promoted:
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // Engine started
fmt.Println(c.Engine.Start()) // also works
}
This promotion has an important consequence for interfaces. If the embedded type satisfies an interface, the outer type automatically satisfies it too:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car satisfies Starter through Engine
}
The Car type never explicitly implements Starter, yet it satisfies the interface because its embedded Engine has the required method. This is how Go achieves behavior reuse without traditional inheritance.
Challenge
EasyLet's build a music player system that demonstrates how methods from embedded types get promoted to the outer type, and how this enables interface satisfaction through composition.
You'll organize your code across two files:
audio.go: Create the building blocks for your music player:- An
AudioPlayerstruct with aBrandfield (string) and aPlay() stringmethod that returns[Brand] playing audio - A
Playableinterface requiring aPlay() stringmethod - A
Smartphonestruct with aModelfield that embedsAudioPlayer - A
Tabletstruct with aModelfield that embedsAudioPlayer
- An
main.go: Create a function calledStartPlaybackthat accepts anyPlayableand returns the result of callingPlay(). Read device information from input, create both a Smartphone and a Tablet, and demonstrate that both satisfy thePlayableinterface through their embeddedAudioPlayer—even though neither type explicitly implementsPlay()itself.
The following inputs will be provided:
- Line 1: Smartphone model name
- Line 2: Smartphone audio brand
- Line 3: Tablet model name
- Line 4: Tablet audio brand
For each device, print its model followed by the result of passing it to StartPlayback.
For example, given iPhone 15, Apple Audio, iPad Pro, and Beats, your output should be:
iPhone 15
Apple Audio playing audio
iPad Pro
Beats playing audioNotice how both Smartphone and Tablet can be passed to StartPlayback because the Play() method from AudioPlayer is automatically promoted to each outer type, satisfying the Playable interface without any additional code.
Cheat sheet
When a struct embeds another type, methods from the embedded type are automatically promoted to the outer struct:
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // embedded
}
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // Engine started
fmt.Println(c.Engine.Start()) // also works
}
If an embedded type satisfies an interface, the outer type automatically satisfies it too:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car satisfies Starter through Engine
}
This enables behavior reuse through composition without traditional inheritance.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Create a StartPlayback function that accepts any Playable
// and returns the result of calling Play()
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Read smartphone model
scanner.Scan()
smartphoneModel := scanner.Text()
// Read smartphone audio brand
scanner.Scan()
smartphoneBrand := scanner.Text()
// Read tablet model
scanner.Scan()
tabletModel := scanner.Text()
// Read tablet audio brand
scanner.Scan()
tabletBrand := scanner.Text()
// TODO: Create a Smartphone with the embedded AudioPlayer
// TODO: Create a Tablet with the embedded AudioPlayer
// TODO: Print smartphone model, then call StartPlayback with the smartphone
// TODO: Print tablet model, then call StartPlayback with the tablet
_ = smartphoneModel
_ = smartphoneBrand
_ = tabletModel
_ = tabletBrand
fmt.Println("TODO: Complete the implementation")
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool