Chapter 5: Working with types
This chapter introduction explains why Go’s type system helps model real-world domains safely and previews the type features used in a recipe application.
After this chapter you will be able to
- Domain Modeling With Types. Explain why primitive types alone may not represent complex real-world concepts and how combining types can express domain entities and relationships.
- Type Safety And Composition. Explain that Go checks types at compile time to catch invalid assignments early, and that it favors building complex types by combining simpler ones rather than through inheritance.
- Custom types for intent. Describe how a Go type declaration uses the type keyword, a chosen name, and an underlying type or type literal to define a type for the domain. Explain how a measurement type or a struct grouping an ingredient’s quantity and unit can make intent clearer and help the compiler catch errors.
- Named types and aliases. Distinguish a named (defined) type from a type alias: a named type creates a distinct type even when it shares an underlying representation, while an alias is another name for the same type.
- Meaningful measurement types. Explain why the recipe application gives measurements domain-specific types such as Magnitude and Unit rather than using plain numbers and strings everywhere.
- Structs as domain models. Explain how a struct groups values that may have different types into one entity, using a recipe Measurement with Magnitude and Unit as the example.
- Inheritance and type embedding. Distinguish traditional inheritance from Go’s type embedding: inheritance lets a child class inherit a parent’s properties and methods, while Go uses embedding to achieve similar behavior without traditional inheritance.
- Embedded type behavior. Explain that embedding one type in another gives the new type access to the embedded type’s fields and methods, while allowing the new type to add its own fields and methods or define a method with the same name.
- Specialized measurement types. Describe how MetricWeight or ImperialVolume can embed Measurement to make the measurement system and unit explicit, and state that an embedded Measurement is supplied when constructing a specialized value.
- Recipe collection type model. Describe how Ingredient, IngredientMeasurement, IngredientList, Step, Recipe, and RecipeBox fit together to represent measured ingredients, recipe steps, and a collection of recipes.
- Consolidated grocery list construction. Trace how a function turns a RecipeBox into a GroceryList by visiting ingredients across recipes and steps, converting measurements to metric, and summing quantities by ingredient.
- Default copy by value. Explain that assigning a value to another variable normally makes a separate copy in Go, so changing the original does not change the new variable. Give the section’s string example or an equivalent example.
- Pointer addresses and dereferencing. Describe a pointer as a value holding another value’s memory address, and explain how `*`, `&`, and dereferencing let code access a shared underlying value.
- Reasons to use pointers. Justify using pointers to avoid copying large structs or arrays and to let functions or methods modify an original value. State that a nil pointer can represent an optional or missing value.
- Methods attached to types. Explain that a Go method is a function attached to a type through a receiver, and describe its declaration using func, a receiver in parentheses, a method name, and parameters.
- Value and pointer receiver choices. Distinguish value receivers, which operate on a copy, from pointer receivers, which can change the original value. Justify choosing a value receiver for a non-mutating conversion such as ImperialWeight.ToMetric, and a pointer receiver for an in-place change such as Recipe.Scale or to avoid copying a large struct.
- Interface contracts and satisfaction. Explain that an interface specifies required method signatures rather than stored data, and that a type satisfies it by providing every required method without an explicit declaration. Distinguish Go's compile-time check from runtime discovery of missing methods in dynamic languages.
- Convertible measurement behavior. Trace how Convertible requires ToImperial() and ToMetric(), how the specialized weight and volume types satisfy it, and how CreateGroceryList calls ToMetric() to normalize ingredients before summing them. Describe what the conversion methods return when the measurement is already in the requested system.
- Single-type interface checks. Explain how a type assertion checks whether an interface value holds a particular concrete type and extracts that value when it does.
- Multiple-type interface handling. Describe how a type switch handles several possible dynamic types of an interface value, including the measurement example and its default case.
- Measurement wrapping through composition. Explain how a new Go type can embed or include an existing measurement type and add behavior such as unit conversion or formatting, without altering the original type.
- Recipe domain type model. Describe how the recipe application uses named types for measurements and units, structs to group related data, embedded types for metric and imperial measurements, and collections for ingredients and steps.
- Recipe behavior through composition. Explain how methods and interfaces let the application create, scale, convert, and summarize recipes, while allowing different types to share behavior. Describe why the section favors type safety and composition over inheritance.
What it builds on
- Summary (chapter 3) · Assumes familiarity with primitive types.
- Structs as types (chapter 3) · The reader has encountered structs as types.
- Slices (chapter 4) · Using slices to hold sequences of values
- Maps (chapter 4) · Using maps to associate keys with values
- Arrays as values (chapter 4) · arrays as values
- Dereferencing pointers (chapter 3) · the basic idea of dereferencing a pointer
- Creating pointers with new and literals (chapter 3) · creating pointers with new and literals
Section by section
Introduction
This chapter introduction explains why Go’s type system helps model real-world domains safely and previews the type features used in a recipe application.
- Domain Modeling With Types: Explain why primitive types alone may not represent complex real-world concepts and how combining types can express domain entities and relationships.
Why it matters: Domain-focused types make a program’s data and intent clearer. - Type Safety And Composition: Explain that Go checks types at compile time to catch invalid assignments early, and that it favors building complex types by combining simpler ones rather than through inheritance.
Why it matters: These choices help make Go programs more robust and easier to maintain. - Recipe Application Chapter Roadmap: Describe how the chapter will use a recipe application to demonstrate defining and composing types, adding behavior with methods and interfaces, and working with pointers and runtime type checks.
Why it matters: The recipe application connects the chapter’s type-system features to one concrete example.
Key terms: primitive types, type system, composite types, domain, type safety, type checking, compile time, composition over inheritance, recipe application, methods
5.1 Modeling a domain with types
Introduces a recipe application to show how custom Go types can represent domain data clearly and safely.
- Recipe domain model: Explain that a domain is the set of types describing an area of work, and identify recipe properties such as ingredients, measurements, and steps that those types could represent.
Why it matters: Thinking in terms of a domain helps you choose types that match the data your application handles. - Custom types for intent: Describe how a Go type declaration uses the type keyword, a chosen name, and an underlying type or type literal to define a type for the domain. Explain how a measurement type or a struct grouping an ingredient’s quantity and unit can make intent clearer and help the compiler catch errors.
Why it matters: Well-chosen types make recipe code easier to read and maintain while helping prevent mistakes.
Key terms: domain, recipe, ingredients, measurements, steps, type keyword, named types, underlying Go type, type literal, structs
5.1.1 Named types
This section introduces named types and typed constants to model recipe measurements more clearly and catch type mistakes at compile time.
- Named types and aliases: Distinguish a named (defined) type from a type alias: a named type creates a distinct type even when it shares an underlying representation, while an alias is another name for the same type.
Why it matters: This distinction explains why the compiler rejects some mixed-type expressions and allows others. - Meaningful measurement types: Explain why the recipe application gives measurements domain-specific types such as Magnitude and Unit rather than using plain numbers and strings everywhere.
Why it matters: Type names make the measurement model easier to read and let the compiler catch accidental misuse. - Typed unit constants: Describe how a named Unit type and constants represent the finite, known set of recipe units in a language without a built-in enum type.
Why it matters: Named unit constants reduce typos and give the application a clear place to extend its unit list.
Key terms: named type, defined type, type alias, underlying representation, explicit conversion, Magnitude, Unit, measurement, metric, imperial
5.1.2 Structs
This section shows how Go structs combine related values into a domain type, how to use their fields, and what happens when fields are omitted.
- Structs as domain models: Explain how a struct groups values that may have different types into one entity, using a recipe Measurement with Magnitude and Unit as the example.
Why it matters: Grouping related values makes a measurement easier to represent and use without separating its amount from its unit. - Struct fields and access: Describe defining a named struct type with named, typed fields, then declaring a value, assigning its fields, and accessing them with dot notation.
Why it matters: Field definitions and dot notation let you create and use the domain types described in this section. - Field visibility and defaults: Distinguish exported capitalized fields from lowercase fields that are unexported outside the package, and explain that omitted struct fields receive their types’ zero values.
Why it matters: These rules explain both who can access a field and what value it has before you explicitly set it.
Key terms: struct, Measurement, Magnitude, Unit, struct type, field, dot notation, exported, unexported, zero value
5.2 Extending types
This section introduces type embedding as Go’s alternative to traditional object-oriented inheritance.
- Inheritance and type embedding: Distinguish traditional inheritance from Go’s type embedding: inheritance lets a child class inherit a parent’s properties and methods, while Go uses embedding to achieve similar behavior without traditional inheritance.
Why it matters: This distinction sets up how Go extends types without a class hierarchy.
Key terms: inheritance, child, subclass, parent, superclass, type embedding
5.2.1 Type embedding
This subsection shows how embedding a general Measurement type creates specialized measurement types that retain its fields and methods while allowing their own behavior.
- Embedded type behavior: Explain that embedding one type in another gives the new type access to the embedded type’s fields and methods, while allowing the new type to add its own fields and methods or define a method with the same name.
Why it matters: Embedding lets you extend a type without using classical inheritance. - Specialized measurement types: Describe how MetricWeight or ImperialVolume can embed Measurement to make the measurement system and unit explicit, and state that an embedded Measurement is supplied when constructing a specialized value.
Why it matters: The example shows how a shared general type can support behavior specific to each kind of measurement.
Key terms: type embedding, embedded type, fields, methods, Measurement, MetricWeight, ImperialVolume, measurement system, unit
5.2.2 Complex types
This section models recipes with named slice and map types, then uses those types to consolidate ingredients into a grocery list.
- Recipe collection type model: Describe how Ingredient, IngredientMeasurement, IngredientList, Step, Recipe, and RecipeBox fit together to represent measured ingredients, recipe steps, and a collection of recipes.
Why it matters: The relationships between these types make the recipe model readable and give later operations a clear structure to work with. - Consolidated grocery list construction: Trace how a function turns a RecipeBox into a GroceryList by visiting ingredients across recipes and steps, converting measurements to metric, and summing quantities by ingredient.
Why it matters: Consistent units and ingredient-based totals make a combined shopping list useful across multiple recipes. - Named collection design tradeoff: Explain why naming slice and map types can make domain code more expressive and support collection-level methods, while requiring more up-front design.
Why it matters: It helps you decide when stronger domain types clarify an application enough to justify their design cost.
Key terms: Ingredient, IngredientMeasurement, IngredientList, Step, Recipe, RecipeBox, GroceryList, Measurement, Convertible, ToMetric()
5.3 Pointer types
This section contrasts Go’s default copying behavior with pointers, then shows how pointers share access to values and when they are useful.
- Default copy by value: Explain that assigning a value to another variable normally makes a separate copy in Go, so changing the original does not change the new variable. Give the section’s string example or an equivalent example.
Why it matters: Copying explains why a change to one variable usually does not affect another. - Pointer addresses and dereferencing: Describe a pointer as a value holding another value’s memory address, and explain how `*`, `&`, and dereferencing let code access a shared underlying value.
Why it matters: These operations explain why a pointer can observe changes to the value it points to. - Reasons to use pointers: Justify using pointers to avoid copying large structs or arrays and to let functions or methods modify an original value. State that a nil pointer can represent an optional or missing value.
Why it matters: These uses help you decide when sharing access is preferable to making a copy. - Pointer creation and struct access: Describe how `new` allocates a value and returns a pointer to it, and how a pointer to a struct can access fields with dot notation because Go dereferences it automatically.
Why it matters: These conveniences make pointers practical to create and use with structs. - Pointer limits and reference types: Distinguish pointers from slices, maps, and channels, whose assignments do not copy their underlying data, and state that Go does not allow pointer arithmetic.
Why it matters: These distinctions prevent you from assuming every assignment copies underlying data or that Go pointers work like C pointers.
Key terms: copy by value, pointer, memory address, dereferencing, *, &, large structs, arrays, nil, new
5.4 Methods and behavior
This section shows how to attach behavior to Go types with methods, choose value or pointer receivers, and see why interfaces are needed to describe capabilities independently of concrete types.
- Methods attached to types: Explain that a Go method is a function attached to a type through a receiver, and describe its declaration using func, a receiver in parentheses, a method name, and parameters.
Why it matters: Methods keep a type’s data and related behavior together. - Value and pointer receiver choices: Distinguish value receivers, which operate on a copy, from pointer receivers, which can change the original value. Justify choosing a value receiver for a non-mutating conversion such as ImperialWeight.ToMetric, and a pointer receiver for an in-place change such as Recipe.Scale or to avoid copying a large struct.
Why it matters: The receiver choice determines whether a method can change the original data and whether it copies a large value. - Concrete methods and abstract capabilities: Explain that methods describe what a particular type can do, but do not by themselves express a capability independently of the concrete type; the section introduces interfaces as the way to discuss such capabilities.
Why it matters: This distinction sets up why the next section introduces interfaces.
Key terms: method, receiver, func, value receiver, pointer receiver, ImperialWeight, ToMetric, MetricWeight, Recipe, methods
5.5 Interfaces
This section explains how Go interfaces define shared behavior and uses Stringer and measurement conversion to show implicit implementation in practice.
- Interface contracts and satisfaction: Explain that an interface specifies required method signatures rather than stored data, and that a type satisfies it by providing every required method without an explicit declaration. Distinguish Go's compile-time check from runtime discovery of missing methods in dynamic languages.
Why it matters: This is what lets Go code accept different types through the behavior they share. - Stringer formatting example: Describe how a String() method on Unit lets fmt.Printf use its custom string representation, such as a unit abbreviation, and state the -er naming convention for single-method interfaces.
Why it matters: Stringer shows how an interface can make an existing formatting function use your type's behavior. - Convertible measurement behavior: Trace how Convertible requires ToImperial() and ToMetric(), how the specialized weight and volume types satisfy it, and how CreateGroceryList calls ToMetric() to normalize ingredients before summing them. Describe what the conversion methods return when the measurement is already in the requested system.
Why it matters: The example shows different measurement types working through one interface in the grocery-list calculation. - Focused interface design: Justify keeping an interface focused on the behavior code needs: Convertible allows different implementations to be used alike without inheritance and leaves room for future measurement types.
Why it matters: A focused contract makes code reusable without forcing unrelated types into a complex hierarchy.
Key terms: interface, method signatures, satisfies, structural typing, compile time, Stringer, String(), fmt.Printf, -er, Convertible
5.6 Checking types
The section shows how to inspect the concrete type held by a Go interface value when code needs type-specific behavior.
- Single-type interface checks: Explain how a type assertion checks whether an interface value holds a particular concrete type and extracts that value when it does.
Why it matters: A type assertion lets you access concrete details without giving up an interface-based design. - Multiple-type interface handling: Describe how a type switch handles several possible dynamic types of an interface value, including the measurement example and its default case.
Why it matters: A type switch keeps logic for several concrete implementations together while retaining an interface as the input.
Key terms: type assertion, interface value, concrete type, ok, type switch, dynamic type, Convertible, MeasurementSystem, default case
5.6.1 Type composition and wrapping
This section shows how Go composition can wrap a measurement type to add behavior without changing the original type.
- Measurement wrapping through composition: Explain how a new Go type can embed or include an existing measurement type and add behavior such as unit conversion or formatting, without altering the original type.
Why it matters: Composition lets you extend behavior while keeping the original type reusable.
Key terms: composition, wrapping, embedding, measurement type
5.6.2 Example: Putting it all together
This section brings together the recipe application’s types and behaviors to show how Go’s type system supports a clear, extensible domain model.
- Recipe domain type model: Describe how the recipe application uses named types for measurements and units, structs to group related data, embedded types for metric and imperial measurements, and collections for ingredients and steps.
Why it matters: These types make the recipe domain explicit in the code. - Recipe behavior through composition: Explain how methods and interfaces let the application create, scale, convert, and summarize recipes, while allowing different types to share behavior. Describe why the section favors type safety and composition over inheritance.
Why it matters: Composition and interfaces let the recipe model gain shared behavior without an inheritance hierarchy.
Key terms: named types, structs, embedded types, collection types, metric and imperial measurements, methods, interfaces, type safety, composition over inheritance, abstraction
5.7 Summary
This summary brings together how Go types model domains, share and modify data, define behavior, and handle concrete types through interfaces.
Where this is used later
- Domain module: Shared domain model (chapter 11) · The shared domain model builds on the idea of representing an area of work with types.
- (chapter intro) (chapter 6) · Introduces types as a way to model and abstract a domain.
- The slices and maps packages (chapter 6) · Domain modeling with named types provides the RecipeBox and GroceryList examples.
- Typed parameters in generics (chapter 6) · The generic Collection extends the idea of interfaces as contracts.
Concepts explained in this chapter
- user-defined typeA defined type is a programmer-declared type that creates a distinct named type with an underlying type and can have its own methods.
- type systemA type system is the set of rules governing the classification of values and the validity of operations on them.
- structThe struct keyword introduces a Go struct type declaration.
- ingredientIngredient is a recipe-domain type representing the name of an ingredient as a distinct named value.
- interfaceAn interface is a contract specifying operations or type-set membership that a type can provide without defining the type's implementation.
- polymorphismPolymorphism is the ability to treat values of different concrete types through a shared interface or abstraction.
- type assertionA type assertion is a Go operation that checks whether an interface value contains a specified concrete type and retrieves the contained value if it does.
- type switchA type switch is a Go control-flow construct that selects a case according to the dynamic type of an interface value.
- StringerStringer is a Go interface conventionally satisfied by a type that provides a String method for custom textual formatting.
- structural typingStructural typing determines interface compatibility from the methods a type provides rather than from an explicit declaration of implementation.
- value receiverA value receiver is a method receiver that operates on a copied value of its receiver type rather than on a pointer to that value.
- type aliasA type alias gives an existing type an alternate name without creating a distinct type.
- methodA method is a function associated with a type that defines behavior operating on values of that type.
- pointerA pointer type is a Go type whose values identify the memory locations of values of a specified target type.
- MapA map is a Go associative data structure that associates keys with values.
- slicingA slice expression is Go syntax that creates a slice view over an index range of an array or slice while sharing the underlying storage.
- value copyCopy by value is an assignment behavior in which a variable receives an independent copy of another value's data.
- composition over inheritanceComposition over inheritance is a design pattern that builds complex types by combining simpler components instead of deriving them from superclasses.
- type safetyType safety is the property that prevents operations from applying values in ways inconsistent with their types.
- struct embeddingStruct embedding is a Go language feature that embeds one struct type in another and promotes the embedded type's fields and methods.
- data typeA type is a specification of the values and operations permitted for a piece of data in a programming language.
- GoGo is an open-source, statically typed programming language designed for efficient compilation, execution, concurrency, and software development tooling.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
