Want to learn how to build better Go applications faster and easier? You can.
Check out my course on the Go Standard Library. You can check it out now for free.
Think of embedding in Go like putting ingredients together to make a delicious dish. In this case, the “ingredients” are types (like structs) that you combine to create a more complex type. It’s a way to extend existing types with new functionality without resorting to inheritance.
In programming terms, embedding means placing the type of another variable inside a struct. This allows the embedded variable to inherit all the properties and methods of the original type, effectively adding functionality to it.
Imagine we have a structure representing a Car
:
type Car struct {
Brand string
Model string
}
We can create a new Vehicle
type that “inherits” from Car
:
type Vehicle struct {
Wheels int
Engine string
// ... other fields
}
Now, let’s embed Vehicle
into CarWithTrailer
:
type CarWithTrailer struct {
Vehicle // Embed the Vehicle type
Trailer bool
}
Let’s break down what’s happening here:
Car
and Vehicle
.CarWithTrailer
struct embeds Vehicle
, meaning it can access all of Vehicle
’s fields and methods.Benefits of Embedding:
Vehicle
type can be used by multiple structs, allowing for code sharing and efficiency.Composition Example:
The example above shows how you can create a new “subtype” CarWithTrailer
that has an “is-a” relationship with the Vehicle
struct.
// Embedding allows us to reuse the properties and methods of the "Vehicle" struct
type Vehicle struct {
Engine string
Wheels int
}
type CarWithTrailer struct {
Vehicle // This is where we embed the "Vehicle" struct
Towing bool
}
func (c *Vehicle) start() {
fmt.Println("Starting the vehicle engine...")
}
func main() {
c := CarWithTrailer{
Vehicle: Vehicle{
Engine: "V8",
Wheels: 4,
},
Towing: true,
}
c.start() // Access the start method from Vehicle
}
In this case, CarWithTrailer
has all the properties of Vehicle
, including its methods.
Car
, the CarWithTrailer
struct inherits them, making the code more structured and readable.Vehicle
already has fields for “engine” and “wheels”, we can reuse the Vehicle
type by simply embedding it in the CarWithTrailer
struct.Common Mistakes Beginners Make:
Vehicle
struct into the CarWithTrailer
type can be used to extend existing functionality, but it doesn’t directly define a hierarchy.Vehicle
struct with methods that describe the functionality of a vehicle, and then embed it into other structs.interface
instead of a struct
.Let’s revisit our initial example and see how it can be used in practice:
type Vehicle struct {
Engine string
Wheels int
}
type Car struct {
Vehicle // This is the "is-a" relationship
}
func (v *Vehicle) startEngine() {
fmt.Println("Starting the vehicle engine...")
}
func main() {
c := Car{
Vehicle: Vehicle{
Engine: "Electric",
Wheels: 4,
},
}
c.startEngine() // Calling startEngine method from Vehicle
}
Example:
type Vehicle struct {
EngineType string
Wheels int
}
type Car struct {
Vehicle // This is the "is-a" relationship
}
func main() {
v := &Vehicle{EngineType: "Internal Combustion", Wheels: 4}
c := &Car{Vehicle: *v}
fmt.Println("Car engine type:", c.EngineType) // Access embedded field
}
Embedding and composition in Go provide powerful ways to organize and reuse code. By understanding and properly using these features, you can write more modular, flexible, and maintainable Go programs. Remember to use interfaces for defining behaviors and to keep your embedded structs small and focused on specific functionalities. Happy coding!