Menu
Coddy logo textTech

Embedding Multiple Structs

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 37 of 107.

Go allows you to embed multiple structs within a single struct, combining behaviors from different sources. This is similar to multiple inheritance in other languages, but with Go's composition approach.

type Logger struct{}

func (l Logger) Log(msg string) string {
    return "LOG: " + msg
}

type Notifier struct{}

func (n Notifier) Notify(msg string) string {
    return "NOTIFY: " + msg
}

type Service struct {
    Name string
    Logger
    Notifier
}

The Service struct now has access to methods from both embedded types:

func main() {
    s := Service{Name: "OrderService"}
    fmt.Println(s.Log("started"))      // LOG: started
    fmt.Println(s.Notify("new order")) // NOTIFY: new order
}

When embedding multiple structs, a naming conflict can occur if two embedded types have methods with the same name. Go doesn't automatically resolve this ambiguity:

type A struct{}
func (A) Greet() string { return "Hello from A" }

type B struct{}
func (B) Greet() string { return "Hello from B" }

type Combined struct {
    A
    B
}

func main() {
    c := Combined{}
    // c.Greet() - compile error: ambiguous selector
    fmt.Println(c.A.Greet())  // Hello from A
    fmt.Println(c.B.Greet())  // Hello from B
}

When conflicts arise, you must explicitly specify which embedded type's method you want to call using the type name as a qualifier.

challenge icon

Challenge

Easy

Let's build a smart home device system that combines multiple capabilities through struct embedding. You'll create a device that can both control lighting and play music by embedding separate controller types.

You'll organize your code across three files:

  • controllers.go: Create two independent controller structs that provide different capabilities:
    • A LightController with a Brightness field (int) and a SetLight(level int) string method that returns Light set to [level]%
    • An AudioController with a Volume field (int) and a SetVolume(level int) string method that returns Volume set to [level]%
    Both controllers should also have a Status() string method—this creates a naming conflict you'll need to handle. LightController.Status() should return Brightness: [Brightness]% and AudioController.Status() should return Volume: [Volume]%.
  • device.go: Create a SmartDevice struct with a Name field that embeds both LightController and AudioController. Add a method called FullStatus() string that returns the device name along with both controller statuses, resolving the ambiguity by explicitly calling each embedded type's Status() method.
  • main.go: Read device configuration from input, create a SmartDevice, use the promoted methods to set light and volume levels, then display the full status.

The following inputs will be provided:

  • Line 1: Device name
  • Line 2: Initial brightness level (integer)
  • Line 3: Initial volume level (integer)
  • Line 4: New brightness level to set (integer)
  • Line 5: New volume level to set (integer)

Your FullStatus() method should return:

[Name] - [LightController.Status()], [AudioController.Status()]

Print the results of calling SetLight and SetVolume (which are promoted from the embedded types), then print the full status.

For example, given Living Room Hub, 50, 30, 75, and 60, your output should be:

Light set to 75%
Volume set to 60%
Living Room Hub - Brightness: 75%, Volume: 60%

Notice how SetLight and SetVolume are directly accessible on SmartDevice through method promotion, but Status() requires explicit qualification because both embedded types have that method.

Cheat sheet

Go allows embedding multiple structs within a single struct, combining behaviors from different sources through composition:

type Logger struct{}

func (l Logger) Log(msg string) string {
    return "LOG: " + msg
}

type Notifier struct{}

func (n Notifier) Notify(msg string) string {
    return "NOTIFY: " + msg
}

type Service struct {
    Name string
    Logger
    Notifier
}

The Service struct has access to methods from both embedded types:

s := Service{Name: "OrderService"}
fmt.Println(s.Log("started"))      // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order

Naming conflicts occur when embedded types have methods with the same name. You must explicitly specify which embedded type's method to call:

type A struct{}
func (A) Greet() string { return "Hello from A" }

type B struct{}
func (B) Greet() string { return "Hello from B" }

type Combined struct {
    A
    B
}

// c.Greet() - compile error: ambiguous selector
c := Combined{}
fmt.Println(c.A.Greet())  // Hello from A
fmt.Println(c.B.Greet())  // Hello from B

Try it yourself

package main

import (
	"fmt"
)

func main() {
	// Read input
	var name string
	fmt.Scanln(&name)

	var initialBrightness int
	fmt.Scanln(&initialBrightness)

	var initialVolume int
	fmt.Scanln(&initialVolume)

	var newBrightness int
	fmt.Scanln(&newBrightness)

	var newVolume int
	fmt.Scanln(&newVolume)

	// TODO: Create a SmartDevice with the given name and initial values

	// TODO: Use the promoted SetLight method and print the result

	// TODO: Use the promoted SetVolume method and print the result

	// TODO: Print the full status using FullStatus() method
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming