Embedding And Composition For Code Reuse: Using Go’s Embedding Feature For Cleaner, More Modular Code

Overview

This chapter delves into Go’s powerful embedding and composition mechanisms, which allow developers to build cleaner, modular, and reusable code without traditional inheritance. Instead of subclassing, Go enables embedding types within structs and interfaces, promoting fields and methods automatically. You will explore how embedding works, how method promotion simplifies code, and how to handle collisions and method overriding effectively. This chapter also covers how embedding can be used to satisfy interfaces, making your code more versatile and maintainable through practical examples.

Embedding And Composition For Code Reuse: Using Go's Embedding Feature For Cleaner, More Modular Code

Go does not have inheritance

It does, however, offer other tools that allow the assembling of multiple components into smaller, more manageable ones.

Embedding Is NOT Inheritance - It's Different

When coming from object-oriented languages, it’s tempting to think of embedding as “inheritance lite.” This mental model will lead you astray.

Inheritance models an is-a relationship and creates a lineage (a subclass is-a superclass).

Embedding models behavior sharing - the outer type gains the embedded type’s methods and fields, but there is no parent-child relationship.

Key differences:

  • No polymorphism: An Admin that embeds User is NOT a User type
  • No method dispatch up the chain: Methods on the embedded type don’t know about the outer type
  • Composition, not lineage: Think “has-a” with automatic delegation, not “is-a”

This distinction matters most when methods call other methods, as we’ll see in the Receiver Chain section.

No Inheritance

When discussing the struct type we saw that a struct contains n numbers of members, of various types.


type User struct {
	Name string
	Age  int
}

This is an example of composition. We are creating a new type, User, out of two other types, string and int.

Composing Complex Types

In Go, we are not limited to just using “primitive” types when composing a new type. We can use “complex” types as well.


type Admin struct {
	User        User
	Permissions map[string]bool
}

func main() {
	a := Admin{
		User: User{
			Name: "Homer",
			Age:  40,
		},
		Permissions: map[string]bool{},
	}

	fmt.Println(a.User.Name)
}

Embedding

In Go we can embed a type by just declaring its type, with no name.


type Admin struct {
	User
	Permissions map[string]bool
}

func main() {
	a := Admin{
		User: User{
			Name: "Homer",
			Age:  40,
		},
		Permissions: map[string]bool{},
	}

	fmt.Println(a.Name)
}

Notice that when creating a new value of type Admin we still had to specify the key User.

Method Promotion

When we embed a type inside of another, both the inner type’s methods and fields get promoted and can be accessed as if they were part of the outer type.


func (u User) Greet() {
	fmt.Printf("Hello, %s\n", u.Name)
}

func main() {
	a := Admin{
		User: User{
			Name: "Homer",
		},
	}

	// Promoted Method from User type
	a.Greet() // Hello, Homer

	// Promoted fields from User Type
	fmt.Println(a.Name, a.Age)
}

Method Collision

When there is a method collision (the inner type and the outer type both implement the same method) the outer type always wins.


func (u User) Greet() {
	fmt.Printf("Hello, %s\n", u.Name)
}

func (a Admin) Greet() {
	fmt.Printf("Admin: %s\n", a.Name)
}

func main() {
	a := Admin{
		User: User{
			Name: "Homer",
		},
	}

	a.Greet() // Admin: Homer
}

Ambiguous Fields

If two inner types define the same field name, neither is promoted.

Given the following code…


package main

import (
	"fmt"
)

type User struct {
	ID   int
	Name string
}

type Guest struct {
	ID    int
	Alias string
}

type Admin struct {
	User
	Guest
}

func main() {
	a := Admin{
		User: User{
			ID:   1,
			Name: "Cory",
		},
		Guest: Guest{
			ID:    -1,
			Alias: "guest",
		},
	}
	fmt.Println(a.ID)
}

…neither field would get promoted, and you would receive the following error:

ambiguous selector a.ID

Ambiguous Methods

If two inner types provide the same method, neither is promoted.

Given the following code…


package main

import (
	"fmt"
)

type User struct {
	Name string
}

func (u User) Greet() {
	fmt.Println("hello: ", u.Name)
}

type Guest struct{}

func (g Guest) Greet() {
	fmt.Println("hello guest")
}

type Admin struct {
	User
	Guest
}

func main() {
	a := Admin{
		User: User{Name: "Cory"},
	}
	a.Greet()
}

…neither method would get promoted, and you would receive the following error:

./ambiguous/main.go:30:3: ambiguous selector a.Greet

The Shallowest Depth Rule

The Go specification uses the term “anonymous fields” (also called embedded types) and defines a precise rule for method/field promotion:

A field or method f of an embedded field is promoted if x.f is a legal selector that denotes that field or method f.

The shallowest depth wins:

  • Depth 0: Fields/methods defined directly on the outer type
  • Depth 1: Fields/methods from directly embedded types
  • Depth 2+: Fields/methods from types embedded within embedded types

When the same name exists at different depths, the shallowest one is promoted. When the same name exists at the same depth from multiple embeddings, it’s ambiguous and both are evicted from the method set.


// This example demonstrates the "shallowest depth" rule from the Go spec.
// When a field or method exists at multiple embedding depths, the
// shallowest one wins.

type Inner struct {
	Value string
}

func (i Inner) Describe() string {
	return "Inner: " + i.Value
}

type Middle struct {
	Inner // depth 2 from Outer
	Value string
}

func (m Middle) Describe() string {
	return "Middle: " + m.Value
}

type Outer struct {
	Middle // Middle.Describe is at depth 1
	Inner  // Inner.Describe is also at depth 1 - AMBIGUOUS!
}

To resolve ambiguity, define the method at depth 0:


// To fix ambiguity, define the method at depth 0 (on Outer itself)
type OuterFixed struct {
	Middle
	Inner
}

func (o OuterFixed) Describe() string {
	return fmt.Sprintf("Outer combining: %s and %s",
		o.Middle.Describe(), o.Inner.Describe())
}

Important: When embedding causes method ambiguity, the struct no longer satisfies interfaces that require those methods - they’re completely evicted, not just hidden.

Method Overriding

When embedding another type we can override their methods with new methods, while still being able to call the original method.


func (u User) String() string {
	return u.Name
}

type Admin struct {
	User
	Permissions map[string]bool
}

func (a Admin) String() string {
	return fmt.Sprintf("Admin: %s", a.User.String())
}

fmt.Println(a.String()) // Admin: Homer

Fixing Ambiguous Methods

To fix it, you would need to defined the Greet method on the Admin type as follows:


package main

import (
	"fmt"
)

type User struct {
	Name string
}

func (u User) Greet() {
	fmt.Println("hello: ", u.Name)
}

type Guest struct{}

func (g Guest) Greet() {
	fmt.Println("hello guest")
}

type Admin struct {
	User
	Guest
}

func (a Admin) Greet() {
	if a.User.Name != "" {
		a.User.Greet()
		return
	}
	a.Guest.Greet()
}

func main() {
	a := Admin{
		User: User{Name: "Cory"},
	}
	a.Greet()
}

Overriding Via Type Conversion

You can also do method overriding via Type Conversion.

Type conversion happens when we assign the value of one data type to another:

You have already seen type conversions, such as when we go from one numeric type to another:


func main() {
	var i uint64 = 10
	var j int = 20

	// We must type convert i from a uint64 to an int in order to add i and j
	k := (int(i) + j)

	fmt.Println(k)
}

You can also create a type just for method overriding, and via type conversion change the behavior in your program:


type prettyString string

func (p prettyString) String() string {
	return fmt.Sprintf("I'm a pretty string: %q", string(p))
}

func main() {
	s := "Hi"
	fmt.Println(s)
	fmt.Println(prettyString(s))
}

Output:


Hi
I'm a pretty string: "Hi"

More Overriding

In this example, we declare an authUser and use it to override the String method to provide sensitive information for only when a user is authenticated.


package main

import (
	"fmt"
	"log"
	"net/http"
)

type User struct {
	ID    int
	First string
	Last  string
	Email string
}

func (u User) String() string {
	return fmt.Sprintf(`{"id":%d","first":"%s","last":"%s"}`, u.ID, u.First, u.Last)
}

type authUser User

func (u authUser) String() string {
	return fmt.Sprintf(`{"id":%d","first":"%s","last":"%s","email":"%s"}`, u.ID, u.First, u.Last, u.Email)
}

func handler(w http.ResponseWriter, r *http.Request) {
	u := User{
		ID:    1,
		First: "Cory",
		Last:  "LaNou",
		Email: "cory@gopherguides.com",
	}
	w.Header().Set("Content-Type", "application/text")

	data := u.String()

	username, password, ok := r.BasicAuth()
	if ok && username == "admin" && password == "password" {
		// Type convert user to an authUser to send all protected information to be serialized in the response.
		data = authUser(u).String()
	}

	w.Write([]byte(data))
}

func main() {
	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

/*

$ curl localhost:8080
{"id":1","first":"Cory","last":"LaNou"}

$ curl --user admin:password localhost:8080
{"id":1","first":"Cory","last":"LaNou","email":"cory@gopherguides.com"}

*/

The Receiver Chain Gotcha

This is the most common source of confusion for developers coming from OOP languages.

When a method on an embedded type calls another method, that call stays at the embedded type’s level. The outer type’s “override” is never called.


// Employee has two methods that call each other
type Employee struct {
	Name string
}

func (e Employee) Greet() {
	fmt.Printf("Hello, I'm ")
	e.Introduce() // calls Employee.Introduce, NOT Manager.Introduce
}

func (e Employee) Introduce() {
	fmt.Println(e.Name)
}

// Manager embeds Employee and tries to "override" Introduce
type Manager struct {
	Employee
	Department string
}

// This method is NEVER called from Greet()!
func (m Manager) Introduce() {
	fmt.Printf("%s, Manager of %s\n", m.Name, m.Department)
}

func main() {
	m := Manager{
		Employee:   Employee{Name: "Alice"},
		Department: "Engineering",
	}

	// Calling Introduce directly works as expected
	fmt.Print("Direct call: ")
	m.Introduce() // Output: Alice, Manager of Engineering

	// But calling Greet uses Employee.Introduce, not Manager.Introduce!
	fmt.Print("Via Greet:   ")
	m.Greet() // Output: Hello, I'm Alice (NOT "Alice, Manager of Engineering")
}

Why this happens: When Greet() executes, its receiver is Employee, not Manager. The method Greet() has no knowledge of Manager - it was defined on Employee and always operates in that context.

The rule: Once you “go down” the embedding chain via a promoted method, you can never go back up. Methods stay at their original depth.

This breaks the classical “override parent method” mental model. In traditional OOP:

class Manager extends Employee
  def introduce()  # override
    "Manager: #{name}"
  end
end
m.greet()  # calls Manager.introduce() via dynamic dispatch

In Go, there is no dynamic dispatch up the embedding chain. If you need this behavior, use interfaces and explicit delegation instead.

Embedding Unexported Types

Even though a type is unexported, if you embed it promotion still occurs:


package models

import "fmt"

// user is unexported
type user struct {
	Name string
	Age  int
}

func (u user) Greet() {
	fmt.Printf("Hello, %s\n", u.Name)
}

type Admin struct {
	user        // embedding the unexported type
	Permissions map[string]bool
}

func DefaultAdmin() Admin {
	return Admin{
		user: user{
			Name: "Homer",
		},
	}

}

Here is the code that you can run the private promoted method from:


package main

import (
	"fmt"
	"training/models"
)

func main() {
	a := models.DefaultAdmin()
	a.Greet()           // Call the `Greet` method that was promoted from the unexported user type
	fmt.Println(a.Name) // All exported/promoted fields are also accessible.
	// a.user.Greet() // this will not compile as `user` is not an exported field
}

Output:


Hello, Homer

Embedding Pointers

You can also embed pointers within structs:


type Admin struct {
	*User
	Perms map[string]bool
}

However, if that pointer is nil and you try to call a promoted method, you could open yourself up to panics:


func main() {
	a := &Admin{}
	fmt.Println(a.String())
}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xd799b]

Embedding Exercise (15 Mins)


package main

import (
	"errors"
	"fmt"
)

// #TODO: Create a struct type called `Email`
// Add the following fields:
// - DisplayName: string
// - UserName: string
// - Domain: string

// #TODO: Create a method on the Email type called `Address` that takes no arguments
// It should return a string in the format:
// `DisplayName <UserName@Domain>`
// You can use `fmt.Sprintf` to create the return string

// #TODO: Create a struct type called Invite
// Add the following fields:
// - ID: int
// - Email (embedded type Email)

func main() {
	// #TODO: initialize a variable called `invite` of Type Invite with the following values:
	// - ID: 1
	// - Email
	//   - Display Name: "John Smith"
	//   - UserName: "jsmith"
	//   - Domain: "yahoo.com"

	if err := SendEmail(invite); err != nil {
		fmt.Printf("error sending email: %s", err)
	}
}

func SendEmail(i Invite) error {
	address := i.Address()
	if address == "" {
		return errors.New("no email address provided")
	}
	fmt.Printf("sent email to: %s", address)
	return nil
}

Embedding Solution


package main

import (
	"errors"
	"fmt"
)

// #TODO: Create a struct type called `Email`
// Add the following fields:
// - DisplayName: string
// - UserName: string
// - Domain: string
type Email struct {
	DisplayName string
	UserName    string
	Domain      string
}

// #TODO: Create a method on the Email type called `Address` that takes no arguments
// It should return a string in the format:
// `DisplayName <UserName@Domain>`
// You can use `fmt.Sprintf` to create the return string
func (e Email) Address() string {
	return fmt.Sprintf("%s <%s@%s>", e.DisplayName, e.UserName, e.Domain)
}

// #TODO: Create a struct type called Invite
// Add the following fields:
// - ID: int
// - Email (embedded type Email)
type Invite struct {
	ID int
	Email
}

func main() {
	// #TODO: initialize a variable called `invite` of Type Invite with the following values:
	// - ID: 1
	// - Email
	//   - Display Name: "John Smith"
	//   - UserName: "jsmith"
	//   - Domain: "yahoo.com"
	invite := Invite{
		ID: 1,
		Email: Email{
			DisplayName: "John Smith",
			UserName:    "jsmith",
			Domain:      "yahoo.com",
		},
	}
	if err := SendEmail(invite); err != nil {
		fmt.Printf("error sending email: %s", err)
	}
}

func SendEmail(i Invite) error {
	address := i.Address()
	if address == "" {
		return errors.New("no email address provided")
	}
	fmt.Printf("sent email to: %s", address)
	return nil
}

Embedding != Interface

Embedding User into Admin makes Admin appear to behave like User, but it does not mean that an Admin can be used where the User type is requested.


func greetUser(u User) {
	fmt.Println(u.Name)
}

greetUser(a)
admin-bad/main.go:25: cannot use a (type Admin) as type User in argument to greetUser

Embedding To Satisfy An Interface

Because of method promotion when embedding, the outer type can satisfy any interfaces that the inner type satisfies.


func (u User) Greet() {
	fmt.Printf("Hello, %s\n", u.Name)
}

type greeter interface {
	Greet()
}

func greet(g greeter) {
	g.Greet()
}

greet(a)

Embedding Interfaces

Embedding doesn’t end with just concrete types, we can also embedded interfaces, making some very powerful types.

type ReadWriter interface {
  Reader
  Writer
}

From the io package in the standard library creates a new interface, ReadWriter, by composing, and embedding, two other interfaces, Reader and Writer.

Embedding Interfaces Within Structs

Interface types can also be embedded into structs as well, and similarly promote their methods onto the struct type. This can be useful for slightly modifying the behavior of an interface or adding methods to a smaller interface to satisfy a wider one.

Io.ReadCloser

Occasionally you’ll encounter the io.ReadCloser interface when working with the standard library:

type ReadCloser interface {
  Reader
  Closer
}

In tests, you’ll typically have a reader built from strings.NewReader:

r := strings.NewReader("foo")

This is easy to create, but it doesn’t have a Close() method. How can we add one?

Adding A Close Method

By embedding the io.Reader interface, we can decorate it with a Close() method that does nothing:


type Closer struct {
	io.Reader
}

func (c Closer) Close() error {
	return nil
}

In fact, this is the definition of io.NopCloser, which is more common to use in practice than implementing it yourself as we’ve done here.

And this is putting the embedded reader to use:


func main() {
	c := Closer{
		Reader: strings.NewReader("Hello, World!"),
	}
	if err := read(c); err != nil {
		fmt.Println(err)
	}

}

func read(r io.ReadCloser) error {
	if _, err := io.Copy(os.Stdout, r); err != nil {
		return err
	}
	return r.Close()
}

Or we could simply use a io.NopCloser:


func main() {

	r := strings.NewReader("Hello, World!")

	if err := read(io.NopCloser(r)); err != nil {
		fmt.Println(err)
	}
}

func read(r io.ReadCloser) error {
	if _, err := io.Copy(os.Stdout, r); err != nil {
		return err
	}
	return r.Close()
}

Overriding Type For Interface

You can also use type conversions to override a method that satisfies an interface. This is often done to change the behavior of that interface.

In this example, if the user is authorized, we send back protected information(email). If the user is not authorized, the email is not serialized out.

We are able to change this behavior by creating our own type that overrides the MarshalJSON method that satisfies the json.Marshaler interface.


package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type User struct {
	ID    int    `json:"id"`
	First string `json:"first,omitempty"`
	Last  string `json:"last,omitempty"`
	Email string `json:"-"` // ingore this field
}

type authUser User

func (a authUser) MarshalJSON() ([]byte, error) {
	// declare a private type to write our data to
	type usr struct {
		ID    int    `json:"id"`
		First string `json:"first,omitempty"`
		Last  string `json:"last,omitempty"`
		Email string `json:"email,omitempty"`
	}

	// Populate our new instance with just the data we need
	us := usr{
		ID:    a.ID,
		First: a.First,
		Last:  a.Last,
		Email: a.Email,
	}

	return json.Marshal(us)

}

func handler(w http.ResponseWriter, r *http.Request) {
	u := User{
		ID:    1,
		First: "Cory",
		Last:  "LaNou",
		Email: "cory@gopherguides.com",
	}
	w.Header().Set("Content-Type", "application/json")

	username, password, ok := r.BasicAuth()
	if ok && username == "admin" && password == "password" {
		// Type convert user to an authUser to send all protected information to be serialized in the response.
		json.NewEncoder(w).Encode(authUser(u))
		return
	}

	json.NewEncoder(w).Encode(u)
}

func main() {
	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

/*

$ curl localhost:8080
{"id":1,"first":"Cory","last":"LaNou"}

$ curl --user admin:password localhost:8080
{"id":1,"first":"Cory","last":"LaNou","email":"cory@gopherguides.com"}

*/

View Model Pattern: Hiding Sensitive Fields

A common use of embedding is the view model pattern for API responses. You embed a database model but shadow sensitive fields to prevent them from being serialized.

Important: Using json:"-" on an embedded field does NOT work as you might expect. The JSON marshaler will still dig into the embedded struct and serialize its fields. You must shadow the fields explicitly.


// User represents a database model with sensitive fields
type User struct {
	ID       int    `json:"id"`
	Name     string `json:"name"`
	Email    string `json:"email"`
	Password string `json:"password"` // Sensitive!
	APIKey   string `json:"api_key"`  // Sensitive!
}

// PublicUser is a view model that hides sensitive fields from API responses.
// Note: json:"-" on an embedded field does NOT work - the marshaler
// still digs into the embedded struct's fields.
type PublicUser struct {
	User
	Password string `json:"-"`        // Hide password
	APIKey   string `json:"api_key,omitempty"` // Hide if empty
}

// NewPublicUser creates a safe-to-serialize view of the user
func NewPublicUser(u User) PublicUser {
	return PublicUser{
		User:     u,
		Password: "", // Shadows User.Password, hidden by json:"-"
		APIKey:   "", // Shadows User.APIKey, omitted when empty
	}
}

The PublicUser type:

  1. Embeds User to get all the safe fields (ID, Name, Email)
  2. Shadows Password with json:"-" to hide it completely
  3. Shadows APIKey with omitempty so it’s omitted when empty

This pattern is especially useful when:

  • Your database models have fields that should never leave the server
  • You need different JSON representations for different API endpoints
  • You want to add computed fields to responses without modifying the model

Note: Go 1.27 (expected August 2026) ships a new encoding/json/v2 package. Among the changes, field name matching becomes case-sensitive when decoding, and nil slices and maps marshal as [] and {} instead of null, which matters for API responses like these. This chapter teaches the original encoding/json package, which remains the default.

Embedding Vs. Fields

There should be a design decision when composing structures in regards to using embedding vs. field.

Embedding

  • Promotes all fields and methods to newly composed type
  • New type can automatically satisfy any interface the embedded type satisfies
  • Requires method overriding to protect any methods you wish to guard (if any)

A common example of embedding is with the use if the io interfaces, such as io.Reader, io.Writer, etc.

Field

  • No promotion of any fields or methods
  • Type can be protected by not exporting it
  • Can create your own methods and call underlying types fields or methods as needed

A common example of using a field to compose is the use of sync.Mutex. This allows for the new type to have a built in locking mechanism without exposing it outside the new type.

Dependency Injection

Dependency Injection is a design pattern that is used to decouple the external logic of your implementation. In go, if you desire to do dependency injection, you must first make sure your code is not tightly coupled.

If we look at the following example, the Service declares a Logger as a field, but uses the actual type from the log package, thus creating what we call a tightly coupled design. We can now only EVER use a log.Logger and nothing else. As a result, in our tests, we have to also create a real logger even though that may not be desirable.


package main

import (
	"log"
	"os"
)

type Service struct {
	Name   string
	Logger *log.Logger
}

func NewService(n string, l *log.Logger) *Service {
	return &Service{
		Name:   n,
		Logger: l,
	}
}

func (s *Service) Init() {
	s.Logger.Printf("initializing : %s\n", s.Name)
}

func (s *Service) Stop() {
	s.Logger.Printf("shutting down: %s\n", s.Name)
}

func main() {
	l := log.New(os.Stdout, "[service] ", log.Lshortfile)
	s := NewService("example", l)
	s.Init()
	s.Stop()
}

Simple Decoupling

By introducing an interface that only defines the methods we intend to use, we can still use a log.Logger, but we can now create our own implementations (or even stubs) for testing:


package main

import (
	"log"
	"os"
)

type Logger interface {
	Printf(format string, v ...any)
	Println(v ...any)
}

type Service struct {
	Name string
	Logger
}

func NewService(n string, l Logger) *Service {
	return &Service{
		Name:   n,
		Logger: l,
	}
}

func (s *Service) Init() {
	s.Logger.Printf("initializing : %s\n", s.Name)
}

func (s *Service) Stop() {
	s.Logger.Printf("shutting down: %s\n", s.Name)
}

func main() {
	l := log.New(os.Stdout, "[service] ", log.Lshortfile)
	s := NewService("example", l)
	s.Init()
	s.Stop()
}

Injecting A Dependency

Now that we have decoupled a real logger, we can create a No-op logger that we could use in testing, or perhaps we want to silence the logger based on a configuration file provided. The following is now possible due to the decoupling of the logger:


package main

type Logger interface {
	Printf(format string, v ...any)
	Println(v ...any)
}

type Service struct {
	Name string
	Logger
}

type nopLogger struct{}

func (n nopLogger) Printf(format string, v ...any) {}
func (n nopLogger) Println(v ...any)               {}

func NewService(n string, l Logger) *Service {
	return &Service{
		Name:   n,
		Logger: l,
	}
}

func (s *Service) Init() {
	s.Logger.Printf("initializing : %s\n", s.Name)
}

func (s *Service) Stop() {
	s.Logger.Printf("shutting down: %s\n", s.Name)
}

func main() {
	//l := log.New(os.Stdout, "[service] ", log.Lshortfile)
	s := NewService("example", nopLogger{})
	s.Init()
	s.Stop()
}

Complex Example

So far, we have looked at a very rudimentary example of using dependency injection. Let’s look at a more complex scenario that might look closer to what a production level construct might represent.


package main

import (
	"log"
	"os"
	"sync"
	"time"
)

type Logger interface {
	Printf(format string, v ...any)
	Println(v ...any)
}

type Service struct {
	Name string
	Logger
	stop     chan struct{}
	complete chan struct{}
	mu       sync.Mutex
}

func NewService(n string, l Logger) *Service {
	stop := make(chan struct{})
	complete := make(chan struct{})
	return &Service{
		Name:     n,
		Logger:   l,
		stop:     stop,
		complete: complete,
		mu:       sync.Mutex{},
	}
}

func (s *Service) Init() {
	s.Logger.Printf("initializing : %s\n", s.Name)
}

func (s *Service) Run() {
	go func() {
		for {
			select {
			case <-s.stop:
				s.Logger.Println("ending process")
				s.mu.Lock()
				defer s.mu.Unlock()
				close(s.complete)
				return
			default:
				time.Sleep(time.Second)
				s.Logger.Println("doing some work...")
			}
		}
	}()
}

func (s *Service) Stop() {
	s.mu.Lock()
	close(s.stop)
	s.mu.Unlock()
	s.Logger.Printf("shutting down: %s\n", s.Name)
	<-s.complete
	s.Logger.Printf("shutdown successful")
}

func main() {
	l := log.New(os.Stdout, "[service] ", log.Lshortfile)
	s := NewService("example", l)
	s.Init()
	s.Run()
	// let the program fun for a while
	time.Sleep(10 * time.Second)
	s.Stop()
}

Interface Exercise (15 Mins)


package main

import (
	"errors"
	"fmt"
)

// #TODO: Declare an interface called `Addressable` that defines the `Address` method
// from the Email

type Invite struct {
	ID int
	Email
}

type Email struct {
	DisplayName string
	UserName    string
	Domain      string
}

func (e Email) Address() string {
	return fmt.Sprintf("%s <%s@%s>", e.DisplayName, e.UserName, e.Domain)
}

// #TODO: Declare a type called `spamTrack` derived from the `Invite` type

// #TODO: create a method `Address` to override the Invite address method
// - Retrieve the address from the original Invite
// - add in `+spam` identifier in the email to change all emails from `name@domain.com` to `name+spam@domain.com`
//   hint: you can use the following command: strings.ReplaceAll(e, "@", "+spam@")
// - return the new `spam tracker` email instead of the original email

func main() {
	invite := Invite{
		ID: 1,
		Email: Email{
			DisplayName: "John Smith",
			UserName:    "jsmith",
			Domain:      "yahoo.com",
		},
	}
	if err := SendEmail(invite); err != nil {
		fmt.Printf("error sending email: %s", err)
	}

	// #TODO: repeat the above command to send an email, but this time override the invite with the new `spamTrack` type
	// to insert a spam tracking email
}

// #TODO: Change the signature of the `SendEmail` function to take
// an `Addressable` interface, instead of the concrete type `Invite`
func SendEmail(i Invite) error {
	address := i.Address()
	if address == "" {
		return errors.New("no email address provided")
	}
	fmt.Printf("sent email to: %s\n", i.Address())
	return nil
}

Interface Solution


package main

import (
	"errors"
	"fmt"
	"strings"
)

// #TODO: Declare an interface called `Addressable` that defines the `Address` method
// from the Email
type Addressable interface {
	Address() string
}

type Invite struct {
	ID int
	Email
}

type Email struct {
	DisplayName string
	UserName    string
	Domain      string
}

func (e Email) Address() string {
	return fmt.Sprintf("%s <%s@%s>", e.DisplayName, e.UserName, e.Domain)
}

// #TODO: Declare a type called `spamTrack` derived from the `Invite` type
type spamTrack Invite

// #TODO: create a method `Address` to override the Invite address method
// - Retrieve the address from the original Invite
// - add in `+spam` identifier in the email to change all emails from `name@domain.com` to `name+spam@domain.com`
//   hint: you can use the following command: strings.ReplaceAll(e, "@", "+spam@")
// - return the new `spam tracker` email instead of the original email

func (s spamTrack) Address() string {
	e := Invite(s).Address()
	e = strings.ReplaceAll(e, "@", "+spam@")
	return e
}

func main() {
	invite := Invite{
		ID: 1,
		Email: Email{
			DisplayName: "John Smith",
			UserName:    "jsmith",
			Domain:      "yahoo.com",
		},
	}
	if err := SendEmail(invite); err != nil {
		fmt.Printf("error sending email: %s", err)
	}

	// #TODO: repeat the above command to send an email, but this time override the invite with the new `spamTrack` type
	// to insert a spam tracking email
	if err := SendEmail(spamTrack(invite)); err != nil {
		fmt.Printf("error sending email: %s", err)
	}
}

// Change the signature of the `SendEmail` function to take
// an `Addressable` interface, instead of the concrete type `Invite`
func SendEmail(a Addressable) error {
	// Do some work here
	address := a.Address()
	if address == "" {
		return errors.New("no email address provided")
	}
	fmt.Printf("sent email to: %s\n", a.Address())
	return nil
}

Stretch Exercise

The following code will output password as clear text in the XML. To avoid this, create a custom type for the password and satisfy the TextMarshaler interface to change the behavior to always encode a password as ********


package main

import (
	"encoding/xml"
	"fmt"
	"log"
)

// TODO: create a custom type called `password` that is derived from the `string` type

// TODO: satisfy the `TextMarshaller` interface on the `password` type to always
//       return a constant value of ********

type User struct {
	ID       int
	Name     string
	Password string // TODO: change the type to your new type of `password`
}

func main() {
	u := User{
		ID:       1,
		Name:     "Jill Rogers",
		Password: "ksd*23k21!",
	}

	b, err := xml.Marshal(u)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))
}

Current Output:

<User><ID>1</ID><Name>Jill Rogers</Name><Password>ksd*23k21!</Password></User>

For more information on how the TextMarshaler is defined, see the TextMarshaler documentation

Stretch Solution


package main

import (
	"encoding/xml"
	"fmt"
	"log"
)

// TODO: create a custom type called `password` that is derived from the `string` type
type password string

// TODO: satisfy the `TextMarshaller` interface on the `password` type to always
//       return a constant value of ********
func (p password) MarshalText() ([]byte, error) {
	return []byte("********"), nil
}

type User struct {
	ID       int
	Name     string
	Password password // TODO: change the type to your new type of `password`
}

func main() {
	u := User{
		ID:       1,
		Name:     "Jill Rogers",
		Password: "ksd*23k21!",
	}

	b, err := xml.Marshal(u)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))
}

Output:

<User><ID>1</ID><Name>Jill Rogers</Name><Password>********</Password></User>

Additional Resources

Talks

Go Specification

  • Struct types - Official spec on embedding (called “anonymous fields”)
  • Selectors - The depth rule for field/method promotion