Chapter 3: Primitive types and operators
The chapter introduces Go’s built-in types as fundamental building blocks and previews the types and operations it will cover.
After this chapter you will be able to
- Primitive types and chapter scope. Explain why Go’s built-in types are called primitive types, and describe the chapter’s main topics: numbers and numeric operations, bool, structs, pointers, strings, and runes.
- Signed and unsigned integers. Explain how signed and unsigned integer types differ in the values they can represent and why unsigned types have a larger positive range at the same size.
- Fixed and architecture-dependent sizes. Distinguish integer types with numeric size suffixes from int and uint in terms of bit size and dependence on the target architecture.
- Integer declarations and literals. Describe the resulting value and type of an integer declared with var or :=, and identify integer literals as fixed values written directly in code that can use several bases.
- Default signed integer choice. Explain why int is the usual first choice: it handles positive and negative values, is efficient for the architecture, and matches common Go APIs and indexing. Describe when int64 or a narrower signed type is worth choosing instead.
- Unsigned wraparound and conversion. Trace why subtracting below zero from an unsigned integer wraps to a large value, and why converting a negative int to uint can produce a similarly surprising result even though a negative literal cannot be passed directly as a uint.
- Negative value function policy. Justify taking an int rather than a uint merely to signal that a function rejects negative inputs. Describe the two policies given here: return an error for an unacceptable negative value, or assign negative values a documented special meaning when appropriate.
- Floating-point types and precision. Explain when floating-point numbers are useful, how float32 and float64 differ, and why Go requires an explicit floating-point size rather than offering a plain float type.
- Floating-point literal forms. Describe how to write floating-point literals using a decimal point or scientific notation, including the section’s ways to express an integer literal as floating-point.
- Special floating-point results. Explain that Go’s standard compiler does not specially check floating-point overflow, division by zero, or mathematically invalid operations; the resulting values follow IEEE-754 and can be checked with math.IsInf and math.IsNaN.
- Floating-point type choice. Explain why float64 is the usual choice for floating-point operations and when float32 is worth considering.
- Complex value construction. Describe how to create complex values with the built-in complex() function or an expression containing an imaginary literal, and explain how the function’s argument types determine whether the result is complex64 or complex128.
- Arithmetic operator type rules. Explain that binary arithmetic operators generally work on numeric types and return the operands’ type, but the remainder operator (%) is an exception. State that operands must have the same type, unless a literal can represent that type.
- Integer bitwise operations. Describe bitwise operations as manipulating individual bits of integer values, and explain that Go’s bit clear operator (&^) clears result bits wherever the second operand has a 1.
- Boolean values and inferred type. Explain that Go’s bool type has the values true and false, defaults to false, and is the type inferred when a short variable declaration assigns a Boolean expression.
- Boolean helpers for conditions. Explain how a helper function can return a bool for use in a conditional, from a simple IsEven check to an IsWorkHours check based on the day and time. Justify putting more complex tests in a named function so callers can reuse the logic and it can change without changing the calling code.
- Struct fields and access. Explain that a struct bundles related values as named fields, and describe declaring fields with the struct keyword and accessing them by name with dot notation.
- Reusable struct types. Justify defining structs as reusable types rather than repeatedly writing one-off struct definitions: reusable types avoid clutter and can be passed around.
- Named struct type reuse. Explain that declaring a struct such as person as a type gives it a meaningful, reusable name, and that a variable can then be declared by providing that type and values for its fields. State that structs are a basis for user-defined types in Go.
- Pointer meaning and purpose. Explain that a pointer represents the location of data rather than the data itself, allowing parts of a program to share data without copying it. State why this is useful for large or complex values and that pointers underlie Go’s maps and slices.
- Pointer declarations and addresses. Describe declaring a pointer by placing * before its target type, its nil zero value, and using & on a value to obtain its address. Explain that the resulting address has type *T when the value has type T, and that a pointer is said to point to the referenced variable.
- Pointer values and dereferencing. Explain that a pointer variable holds an address, while prefixing it with * dereferences it to access or modify the value at that address.
- Direct pointer creation choices. Explain that a pointer can be created without an intermediate variable by taking the address of a literal or by using the built-in new function. Describe why a pointer to a struct literal is usually preferred for structs and why new remains useful for built-in types such as int.
- Read-only string bytes. Describe a Go string as essentially a read-only array of bytes for text, and distinguish those bytes from how the text is interpreted for display. State that modification behavior is another way strings differ from types discussed earlier in the chapter.
- Interpreted string contents. Describe an interpreted string as a double-quoted string literal that can be assigned to a string variable. Explain that it can contain printable UTF-8 characters and use escape codes for nonprintable characters or Unicode codepoints.
- Raw string syntax and behavior. Describe how to write a raw string with backticks and explain that it can span multiple lines, treats escape codes literally, and supports raw Unicode characters.
- String comparison and concatenation. Explain that Go strings can be compared with standard comparison operators, with less-than and greater-than determined by byte values, and joined using the + operator.
- String length in bytes. Explain that len returns a string’s byte length, not its number of characters, because Go stores the string’s UTF-8 bytes. Describe why those counts can match for an ASCII string but differ for a string containing non-ASCII characters.
- Bytewise string loop limits. Explain why a traditional for loop that accesses a string byte by byte appears to work for ASCII but does not reliably retrieve individual characters from a Unicode string. State that accessing those characters requires a different approach, without assuming this section has taught that approach yet.
- String range loop results. Explain that ranging over a string yields each character as a rune and the index where that character starts, rather than yielding individual bytes.
- Rune representation of characters. Explain that rune is an alias for int32, large enough to represent a single character in a string, and that it holds the Unicode character’s value rather than its encoding.
- Rune slice character indexing. Explain that converting a string to a []rune gives another way to access its characters, with each slice index corresponding to a character’s position in the printed string.
What it builds on
- Variable declaration (chapter 2) · Earlier variable declarations using var and :=.
- Basic error handling and the log package (chapter 2) · Introduces basic error handling.
- Slices and len (chapter 2) · Earlier use of the built-in len function.
- Basic for loops (chapter 2) · Basic for loops.
Section by section
Introduction
The chapter introduces Go’s built-in types as fundamental building blocks and previews the types and operations it will cover.
- Primitive types and chapter scope: Explain why Go’s built-in types are called primitive types, and describe the chapter’s main topics: numbers and numeric operations, bool, structs, pointers, strings, and runes.
Why it matters: These types are basic tools you will use throughout Go programs.
Key terms: primitive types, built-in types, statically typed, numeric operations, bool, structs
3.1 Integer types
This section introduces Go’s integer types, their sizes and aliases, and how to declare and write integer values.
- Signed and unsigned integers: Explain how signed and unsigned integer types differ in the values they can represent and why unsigned types have a larger positive range at the same size.
Why it matters: The distinction helps you understand the range an integer type can hold. - Fixed and architecture-dependent sizes: Distinguish integer types with numeric size suffixes from int and uint in terms of bit size and dependence on the target architecture.
Why it matters: The type name tells you whether its size stays fixed across architectures. - Byte and rune aliases: Describe the underlying integer types of byte and rune and the contexts their names signal.
Why it matters: These names make it clearer whether code is handling bytes of data or Unicode characters. - Integer declarations and literals: Describe the resulting value and type of an integer declared with var or :=, and identify integer literals as fixed values written directly in code that can use several bases.
Why it matters: Declaration syntax determines an integer variable’s type and, when no value is supplied, its initial value.
Key terms: int, uint, signed, unsigned, two’s complement, int64, uint8, architecture-independent, architecture-dependent, byte
3.1.1 Choosing an integer type
This section explains how to choose Go integer types, why unsigned integers can cause surprising results, and when unsigned types are useful.
- Default signed integer choice: Explain why int is the usual first choice: it handles positive and negative values, is efficient for the architecture, and matches common Go APIs and indexing. Describe when int64 or a narrower signed type is worth choosing instead.
Why it matters: Choosing int by default avoids unnecessary conversions and premature optimization. - Unsigned wraparound and conversion: Trace why subtracting below zero from an unsigned integer wraps to a large value, and why converting a negative int to uint can produce a similarly surprising result even though a negative literal cannot be passed directly as a uint.
Why it matters: These surprises can turn an apparent nonnegative-value check into a hard-to-find bug. - Negative value function policy: Justify taking an int rather than a uint merely to signal that a function rejects negative inputs. Describe the two policies given here: return an error for an unacceptable negative value, or assign negative values a documented special meaning when appropriate.
Why it matters: An explicit policy lets callers understand and safely handle negative inputs instead of relying on unsigned conversion. - Unsigned bitwise type choice: Explain why unsigned integers are useful as bit fields, and why bitwise work generally favors fixed-size unsigned types over architecture-dependent uint. Contrast how unsigned and signed values fill bits when shifted right.
Why it matters: Predictable bit sizes and shifts make bit manipulation easier to reason about.
Key terms: int, int64, int32, int16, int8, len(), unsigned integers, wrap around, uint, two’s complement
3.2 Floating-point number types
This section introduces Go’s floating-point types, their precision tradeoff, declaration defaults, and ways to write floating-point literals.
- Floating-point types and precision: Explain when floating-point numbers are useful, how float32 and float64 differ, and why Go requires an explicit floating-point size rather than offering a plain float type.
Why it matters: Choosing a floating-point type means balancing range, precision, and memory use. - Floating-point declaration defaults: Describe the value of an uninitialized floating-point variable declared with var and the default type of a floating-point number declared with a short declaration.
Why it matters: These defaults help you predict the value and type produced by a declaration. - Floating-point literal forms: Describe how to write floating-point literals using a decimal point or scientific notation, including the section’s ways to express an integer literal as floating-point.
Why it matters: Literal notation tells the compiler when you intend a floating-point value rather than an integer.
Key terms: floating-point, IEEE-754, float32, float64, precision, var, short declaration, zero value, floating-point literal, decimal point
3.2.1 Special floating-point values
This section explains how Go represents exceptional floating-point results and how to check for them.
- Special floating-point results: Explain that Go’s standard compiler does not specially check floating-point overflow, division by zero, or mathematically invalid operations; the resulting values follow IEEE-754 and can be checked with math.IsInf and math.IsNaN.
Why it matters: These checks help you detect invalid or out-of-range floating-point results.
Key terms: IEEE-754, math.IsInf, math.IsNaN, overflow
3.2.2 Choosing a floating-point type
This section recommends float64 as the default floating-point type and explains when float32 may be justified.
- Floating-point type choice: Explain why float64 is the usual choice for floating-point operations and when float32 is worth considering.
Why it matters: This helps you avoid giving up precision without a measured benefit.
Key terms: float64, float32, precision, performance, memory consumption, bottleneck
3.3 Complex number types
This section introduces Go’s native complex number types, how to create them, and how to extract their real and imaginary components.
- Complex number representation: Explain that a Go complex value has real and imaginary floating-point components, and that complex64 and complex128 use twice the memory of their corresponding float types.
Why it matters: The two-component representation explains both complex values and their sizes. - Complex value construction: Describe how to create complex values with the built-in complex() function or an expression containing an imaginary literal, and explain how the function’s argument types determine whether the result is complex64 or complex128.
Why it matters: Creation syntax and type rules let you recognize and construct complex values correctly. - Complex component extraction: Explain that the built-in real() and imag() functions extract a complex value’s floating-point components, returning float32 or float64 according to its complex type.
Why it matters: These functions provide a way to get the individual values back after constructing a complex number.
Key terms: complex64, complex128, real part, imaginary part, complex(), imaginary literal, real(), imag(), float32, float64
3.4 Mathematical operators
This section explains which Go numeric types support arithmetic and bitwise operators, the type rules for arithmetic, and how bit clear works.
- Arithmetic operator type rules: Explain that binary arithmetic operators generally work on numeric types and return the operands’ type, but the remainder operator (%) is an exception. State that operands must have the same type, unless a literal can represent that type.
Why it matters: These rules help you predict whether a numeric expression is valid and what type it produces. - Integer bitwise operations: Describe bitwise operations as manipulating individual bits of integer values, and explain that Go’s bit clear operator (&^) clears result bits wherever the second operand has a 1.
Why it matters: It distinguishes operations on integer bits from general arithmetic and makes Go’s unusual &^ operator interpretable.
Key terms: binary arithmetic operators, remainder, modulo, literals, bitwise operations, bit clear operator, &^, bitwise OR
3.5 The bool type
Introduces Go’s bool type, its values and zero value, and the type inferred for Boolean expressions.
- Boolean values and inferred type: Explain that Go’s bool type has the values true and false, defaults to false, and is the type inferred when a short variable declaration assigns a Boolean expression.
Why it matters: This helps you recognize the type and initial value of Boolean variables in Go.
Key terms: bool, true, false, zero value, Boolean expression, short variable declaration
3.5.1 Boolean functions
This section shows how Boolean-returning helper functions make conditions readable and keep more complex decision logic in one place.
- Boolean helpers for conditions: Explain how a helper function can return a bool for use in a conditional, from a simple IsEven check to an IsWorkHours check based on the day and time. Justify putting more complex tests in a named function so callers can reuse the logic and it can change without changing the calling code.
Why it matters: A named Boolean helper can make a decision clear while keeping its underlying rules in one place.
Key terms: bool, Boolean helper function, conditional logic, IsEven, IsWorkHours
3.6 Struct types
This section introduces structs as bundles of named fields, shows how to access and initialize them, and explains why reusable struct types are preferable to repeated one-off definitions.
- Struct fields and access: Explain that a struct bundles related values as named fields, and describe declaring fields with the struct keyword and accessing them by name with dot notation.
Why it matters: Named fields let you keep related values together while still working with each value separately. - Struct literal initialization: Describe initializing a struct with a short declaration and a struct literal that supplies field names and values, including the required trailing comma in the shown multiline form.
Why it matters: Struct literals provide a direct way to create a value with its fields already filled in. - Reusable struct types: Justify defining structs as reusable types rather than repeatedly writing one-off struct definitions: reusable types avoid clutter and can be passed around.
Why it matters: Reusable struct types make code less tedious when the same shape of data is needed more than once.
Key terms: struct, fields, struct keyword, dot notation, struct literal, short declaration, trailing comma, one-off structs, struct types
3.6.1 Structs as types
The section shows how naming a struct type makes it reusable and simplifies variable declarations.
- Named struct type reuse: Explain that declaring a struct such as person as a type gives it a meaningful, reusable name, and that a variable can then be declared by providing that type and values for its fields. State that structs are a basis for user-defined types in Go.
Why it matters: Named struct types let you reuse a definition instead of describing the structure each time.
Key terms: structure type, person, user-defined types, field
3.7 Pointer types
Introduces pointers as typed references to memory locations and shows how to declare a pointer, recognize its nil zero value, and obtain an address with &.
- Pointer meaning and purpose: Explain that a pointer represents the location of data rather than the data itself, allowing parts of a program to share data without copying it. State why this is useful for large or complex values and that pointers underlie Go’s maps and slices.
Why it matters: This distinction explains why passing a pointer differs from passing a copy of a value. - Pointer declarations and addresses: Describe declaring a pointer by placing * before its target type, its nil zero value, and using & on a value to obtain its address. Explain that the resulting address has type *T when the value has type T, and that a pointer is said to point to the referenced variable.
Why it matters: These are the basics for recognizing whether a pointer has a target and assigning it an address.
Key terms: pointer, memory address, map, slice, *T, nil, address operator, &, points to
3.7.1 Dereferencing pointers
This section explains how to distinguish a pointer’s address from the value it points to and how to access or modify that value.
- Pointer values and dereferencing: Explain that a pointer variable holds an address, while prefixing it with * dereferences it to access or modify the value at that address.
Why it matters: The distinction tells you whether an expression refers to an address or the value stored there.
Key terms: pointer, address, dereferencing, *variableName
3.7.2 Creating pointers with new and literals
This subsection compares ways to create pointers directly and explains when to prefer a struct literal or new.
- Direct pointer creation choices: Explain that a pointer can be created without an intermediate variable by taking the address of a literal or by using the built-in new function. Describe why a pointer to a struct literal is usually preferred for structs and why new remains useful for built-in types such as int.
Why it matters: Choosing between these forms lets you initialize a pointer in the way that best fits its type.
Key terms: address operator, literal, new, struct fields, built-in types
3.8 The string type
Introduces Go’s string type as read-only bytes for text and explains why Unicode and byte interpretation matter.
- Unicode support for text: Explain that Go uses a dedicated string type and Unicode support to display and process messages in a variety of languages.
Why it matters: This gives context for the section’s later discussion of Unicode strings. - Read-only string bytes: Describe a Go string as essentially a read-only array of bytes for text, and distinguish those bytes from how the text is interpreted for display. State that modification behavior is another way strings differ from types discussed earlier in the chapter.
Why it matters: Thinking of strings as bytes helps explain why displaying or changing text needs care.
Key terms: Unicode, string, read-only array of bytes
3.8.1 Interpreted strings
This subsection introduces double-quoted interpreted string literals and the characters and escape codes they can contain.
- Interpreted string contents: Describe an interpreted string as a double-quoted string literal that can be assigned to a string variable. Explain that it can contain printable UTF-8 characters and use escape codes for nonprintable characters or Unicode codepoints.
Why it matters: This helps you recognize how Go represents characters that cannot be typed directly into a string.
Key terms: interpreted string, UTF-8, escape codes, Unicode, codepoint
3.8.2 Raw string values
This subsection introduces Go raw strings and how their contents are interpreted.
- Raw string syntax and behavior: Describe how to write a raw string with backticks and explain that it can span multiple lines, treats escape codes literally, and supports raw Unicode characters.
Why it matters: These rules help you recognize when a string’s text will be preserved rather than interpreted as escapes.
Key terms: raw string, backtick characters, escape codes, raw Unicode characters
3.8.3 String operations
This subsection introduces string comparison by byte values and concatenation with the + operator.
- String comparison and concatenation: Explain that Go strings can be compared with standard comparison operators, with less-than and greater-than determined by byte values, and joined using the + operator.
Why it matters: These are the basic operators for ordering and joining strings in Go.
Key terms: comparison operators, byte values, concatenated, +
3.8.4 len() and Unicode characters
This section explains why Go’s len function and byte-by-byte loops behave differently for ASCII and multibyte Unicode strings.
- String length in bytes: Explain that len returns a string’s byte length, not its number of characters, because Go stores the string’s UTF-8 bytes. Describe why those counts can match for an ASCII string but differ for a string containing non-ASCII characters.
Why it matters: This distinction explains why a short-looking string can have a surprising length. - Variable UTF-8 byte sizes: Describe how the section uses fmt.Printf with %x to show a string’s bytes, and state that a character can occupy one to four bytes. Explain that UTF-8’s compatibility with ASCII makes an ASCII character such as A one byte long.
Why it matters: Seeing the bytes makes the difference between character count and byte length concrete. - Bytewise string loop limits: Explain why a traditional for loop that accesses a string byte by byte appears to work for ASCII but does not reliably retrieve individual characters from a Unicode string. State that accessing those characters requires a different approach, without assuming this section has taught that approach yet.
Why it matters: It prevents byte-by-byte code from treating parts of a multibyte character as separate characters.
Key terms: len, bytes, UTF-8, ASCII, backing array, fmt.Printf, %x, traditional for loop, Unicode
3.8.5 Iterating Unicode strings using a range loop
This subsection explains how a range loop iterates over a Unicode string one character at a time.
- String range loop results: Explain that ranging over a string yields each character as a rune and the index where that character starts, rather than yielding individual bytes.
Why it matters: It helps you process characters that occupy different numbers of bytes.
Key terms: range loop, string, index, rune value, bytes
3.8.6 The rune type
Explains how runes represent Unicode characters independently of their string encoding and points to packages for inspecting them.
- Rune representation of characters: Explain that rune is an alias for int32, large enough to represent a single character in a string, and that it holds the Unicode character’s value rather than its encoding.
Why it matters: This distinction lets you reason about characters without confusing them with their encoded bytes. - Unicode character inspection packages: Describe how the unicode and unicode/utf8 packages can provide more information about a character once you have its rune.
Why it matters: These packages offer ways to inspect characters beyond simply comparing their rune values.
Key terms: rune, int32, Unicode character, encoding, unicode, unicode/utf8
3.8.7 Converting a string to a slice of runes
This section shows how converting a Go string to a slice of runes lets you index its characters by position.
- Rune slice character indexing: Explain that converting a string to a []rune gives another way to access its characters, with each slice index corresponding to a character’s position in the printed string.
Why it matters: Rune indexing helps when you need a character’s position rather than a byte position.
Key terms: rune, []rune, slice of runes, character position
3.9 Summary
The chapter summary recaps how Go’s primitive types support everyday values, conditional logic, grouped data, references, and text.
Where this is used later
- Handling errors (chapter 7) · Develops handling errors returned for invalid inputs.
- Setting and accessing map values (chapter 4) · The bool type and its false zero value
- Structs (chapter 5) · Uses structs when modeling a domain with types.
- Key and value types (chapter 4) · Structs as types
- Modeling a domain with types (chapter 5) · The reader has encountered structs as types.
- Slices (chapter 4) · The discussion of slices builds on pointers’ role in sharing data.
- Pointer types (chapter 5) · The later treatment of pointer types builds on this introduction.
- Arrays as values (chapter 4) · Pointers hold references to memory.
Concepts explained in this chapter
- integerAn integer type represents whole-number values without a fractional component.
- unsigned integerAn unsigned integer is an integer type that represents nonnegative whole-number values only.
- bit operationA bitwise operation computes a result by manipulating the individual binary bits of integer operands.
- int64int64 is Go's signed integer type that represents values using exactly 64 bits.
- integer wraparoundInteger wraparound is the behavior in which arithmetic outside an integer type's representable range produces a value obtained by cycling through its fixed-width bit representation.
- runeA rune is Go's alias for int32 that represents a Unicode code point independently of its encoded representation.
- byte typeA byte is an eight-bit unsigned integer value used in Go to represent raw binary data.
- pointerA pointer type is a Go type whose values identify the memory locations of values of a specified target type.
- stringA string is Go's immutable sequence-of-bytes type, conventionally used to hold encoded text.
- type aliasA type alias gives an existing type an alternate name without creating a distinct type.
- structThe struct keyword introduces a Go struct type declaration.
- len()len is a Go built-in function that returns the number of bytes in a string or the number of elements in a supported collection.
- data typeA type is a specification of the values and operations permitted for a piece of data in a programming language.
- Go compilerThe Go compiler is the program that checks Go source code and translates it into executable machine code or an intermediate representation.
- 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.
- range loopA range loop is a Go for-loop form that iterates over the elements or entries of a supported collection and can provide each element's index and value.
- backing arrayA backing array is the underlying contiguous storage that holds the elements accessible through a slice and any reserved capacity.
- arrayAn array is a fixed-length, indexed sequence of values of one element type that is itself a value in Go.
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.
