Embedding

Embedding

Declaring Fields

Sample program to show how what we are doing is NOT embedding a type but just using a type as a field.

import "fmt"

// user defines a user in the program.
type user struct {
	name  string
	email string
}

// notify implements a method notifies users
// of different events.
func (u *user) notify() {
	fmt.Printf("Sending user email To %s<%s>\n", u.name, u.email)
}

// admin represents an admin user with privileges.
type admin struct {
	person user // NOT Embedding
	level  string
}

person user is not embedding. All we do here just create a person field based on that other concrete type named user.

func main() {
	// Create an admin user
	ad := admin{
		person: user{
			name:  "john smith",
			email: "john@yahoo.com",
		},
		level: "super",
	}

    // We can access fields methods.
	ad.person.notify()
}

Create an admin user using struct literal. Since person also has struct type, we use another literal to initialize it.

We call notify through the person field through the admin type value.

Embedding types

Sample program to show how to embed a type into another type and the relationship between the inner and outer type.

// user defines a user in the program.
type user struct {
	name  string
	email string
}

// notify implements a method notifies users
// of different events.
func (u *user) notify() {
	fmt.Printf("Sending user email To %s<%s>\n",
		u.name,
		u.email)
}

// admin represents an admin user with privileges.
type admin struct {
	user  // Embedded Type
	level string
}

Notice that we don’t use the field person here anymore. We are now embedding a value of type user inside value of type admin. This is an inner-type-outer-type relationship where user is the inner type and admin is the outer type.

Inner type promotion

What special about embedding in Go is that we have inner type promotion mechanism. In other words, anything related to the inner type can be promoted up to the outer type. It will mean more in the construction below.

func main() {
	// Create an admin user.
	ad := admin{
		user: user{
			name:  "john smith",
			email: "john@yahoo.com",
		},
		level: "super",
    }
}

We are now constructing outer type admin and inner type user. This inner type value now looks like a field, but it is not a field. We can access it through the type name like a field. We are initializing the inner value through the struct literal of user.

// We can access the inner type's method directly.
ad.user.notify()

// The inner type's method is promoted.
ad.notify()

Because of inner type promotion, we can access the notify method directly through the outer type. Therefore, the output will be the same.

Inner type promotion is giving us a sense of type reuse, but will use it for much more than that.

Embedded types and interfaces

Sample program to show how embedded types work with interfaces.

// notifier is an interface that defined notification
// type behavior.
type notifier interface {
	notify()
}

// user defines a user in the program.
type user struct {
	name  string
	email string
}

// notify implements a method notifies users of different events using a
// pointer receiver.
func (u *user) notify() {
	fmt.Printf("Sending user email To %s<%s>\n",
		u.name,
		u.email)
}

// admin represents an admin user with privileges.
type admin struct {
	user
	level string
}

func main() {
	// Create an admin user.
	ad := admin{
		user: user{
			name:  "john smith",
			email: "john@yahoo.com",
		},
		level: "super",
	}

	// Send the admin user a notification.
	// The embedded inner type's implementation of the
	// interface is "promoted" to the outer type.
	sendNotification(&ad)
}

We are passing the address of outer type value &ad. Because of inner type promotion, the outer type now implements all the same contract as the inner type.

Embedding does not create a subtyping relationship. This isn’t base derived class stuff like you see in OOP languages. admin is admin and useris still user. The behavior that inner type value uses, the outer type exposes it as well. It means that outer type value can implement the same interface/same contract as the inner type.

We are getting type reuse. We are not mixing or sharing state but extending the behavior up to the outer type.

// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) {
	n.notify()
}

We have our polymorphic function here. Because of inner type promotion, this admin value now satisfies all the same interfaces.

Outer and inner type interface implementations

Sample program to show what happens when the outer and inner type implement the same interface.

// notifier is an interface that defined notification type behavior.
type notifier interface {
	notify()
}

// user defines a user in the program.
type user struct {
	name  string
	email string
}

// notify implements a method notifies users of different events.
func (u *user) notify() {
	fmt.Printf("Sending user email To %s<%s>\n",
		u.name,
		u.email)
}

// admin represents an admin user with privileges.
type admin struct {
	user
	level string
}

// notify implements a method notifies admins of different events.
func (a *admin) notify() {
	fmt.Printf("Sending admin Email To %s<%s>\n",
		a.name,
		a.email)
}

We now have two different implementations of notifier interface, one for the inner type, one for the outer type. Because the outer type now implements that interface, the inner type promotion doesn’t happen. We have overwritten through the outer type anything that inner type provides to us.

func main() {
	// Create an admin user.
	ad := admin{
		user: user{
			name:  "john smith",
			email: "john@yahoo.com",
		},
		level: "super",
	}

	// Send the admin user a notification.
    // The embedded inner type's implementation of the interface is NOT
    // "promoted" to the outer type.
	sendNotification(&ad)

	// We can access the inner type's method directly.
	ad.user.notify()

	// The inner type's method is NOT promoted.
	ad.notify()
}

// sendNotification accepts values that implement the notifier interface and
// sends notifications.
func sendNotification(n notifier) {
	n.notify()
}