Chapter 2: Diving into Go
The chapter introduces a hands-on Go project that uses a real-world-style utility to teach language fundamentals and introduce Go’s tooling.
After this chapter you will be able to
- Hands-on utility project. Describe how the chapter teaches Go by building one piece of software modeled on a real-world utility, introducing source structure and language fundamentals along the way rather than presenting reference material alone.
- Go word-counting project. Explain that the chapter will build a Go program to count words in text files, rather than simply use the existing `wc` command-line tool, which counts lines and words.
- Hands-on Go setup. Explain why the book encourages coding along, distinguish the minimum setup from tools that may improve the experience, and state where to find current setup recommendations.
- Command-line Go tool access. Explain that even if an editor installs Go, the Go tools must still be accessible from the command line because the book uses them regularly. State that installing Go for the operating system is a safe way to ensure that access.
- Git as a Go prerequisite. Explain that Git is a revision control system and that the Go toolchain uses it by default to fetch dependencies from repositories such as GitHub. State that, if an editor has not installed Git, you can download and install it from the Git website.
- Go toolchain installation. Describe how to install Go from the official installation page and name what the distribution includes: the standard library, compiler, and command-line tools for building and testing software.
- Terminal access options. Describe a terminal as the interface for typing commands and identify where to find one: in an IDE or through the operating system. Mention that other terminal applications are optional if the built-in one feels bare-bones.
- Project root setup steps. State that setting up a Go project starts by creating a directory for the code, then initializing that directory as a Go module.
- Wordcount project root. Explain that the word-count project starts with a directory named wordcount, which serves as the project root where its code lives and helps Go find and build it. State that a containing gobook directory is optional.
- Wordcount module initialization. Describe running `go mod init gobook/wordcount` in the `wordcount` directory to initialize the project. Explain that `mod` is a subcommand of the `go` tool and that the module name’s final path segment matches the directory name.
- Go module file contents. Explain that initialization creates `go.mod` in the project’s main module directory and that the Go toolchain refers to it when building code there or beneath it. Describe the `module` directive as the module name and the `go` directive as the Go version used for the software.
- Initial space counting approach. Explain that the first version counts spaces in a string as a simple starting point for counting words, and that later iterations will improve it.
- First space-counting program. Describe creating main.go and explain that the program examines each character in a text variable to count spaces, with rough edges to improve later.
- Building a Go executable. Explain how to run go build from the directory containing main.go and what it produces: it compiles all .go files in that directory into an executable named after the directory by default.
- Running with go run. Explain that you can use go run from the project directory to run the program without first invoking go build, making it useful for quick tests as the program changes.
- Purpose of standard formatting. Explain that gofmt, bundled with Go, gives Go code a consistent appearance regardless of individual style preferences. Describe how this reduces style debates and makes unfamiliar code easier to read.
- Checking and applying gofmt. Describe using gofmt -d on main.go to display differences from standard formatting, with no output indicating that the file is already formatted. State that gofmt -w modifies the file in place, though an editor may run the formatter automatically on save.
- Executable package and entry point. Describe how declaring package main marks the files as an executable program, and how Go looks across the main-package files in a directory for the main function that starts the program. State that both are needed to produce a command-line executable.
- Import purpose and placement. Explain that an import declaration names packages used by the current package so the Go build toolchain can find and build needed dependencies, and that imports come directly after the package declaration.
- Block visibility rules. Explain that braces delimit blocks such as functions and loops, and that a name declared in a block is visible after its declaration within that block and in inner blocks—not in unrelated blocks or before its declaration.
- Package and universe scopes. Distinguish the package block, whose package-level declarations are visible throughout the package, from the universe block, which contains Go’s built-in functions, types, and constants. Explain that an inner scope can see names in its surrounding scopes.
- Explicit declarations and zero values. Explain how var declares a variable with an explicit type and how an uninitialized variable receives a zero value, such as 0 for an int.
- Short declarations and type inference. Describe how := declares and assigns a new variable in one step, with Go inferring its type from the assigned value.
- Shadowing across block scopes. Explain how using := inside an if block can create a new variable with the same name as an outer variable, leaving the outer value unchanged after the block.
- Go comment styles and documentation. Describe Go’s two comment styles and explain why comments intended as documentation should use complete sentences and proper punctuation.
- Three-part loop execution. Describe the init, condition, and post parts of the three-part for loop: init runs once, the Boolean condition is checked before each iteration, and post runs after each iteration. State that init and post are optional.
- Space-counting loop example. Trace how the example uses i to track a position in the string, checks the character at that position against a space, and increments numSpaces when they match as it proceeds through the string.
- Cross-package function calls. Explain how `fmt.Println` calls `Println` from the `fmt` package and prints its arguments in order, separated by spaces.
- Reliable word counting approach. Explain why counting spaces can produce the wrong word count when text has extra spaces, and describe using strings.Fields to split the text into words instead of counting spaces in a loop.
- Strings package purpose and uses. Explain that the standard library strings package manipulates strings, unlike fmt, which primarily prints and formats them. Describe uses such as changing case, building strings piece by piece, and splitting them.
- Finding package documentation. Explain how to use go doc to read package documentation, what it shows by default, and what the -all flag adds. Describe pkg.go.dev as an online alternative.
- Multiple package import directive. Explain how to add strings alongside fmt so the program can use strings.Fields: either write a second import directive or put both package paths inside one parenthesized import directive.
- Fields result as a slice. Explain that strings.Fields takes one string and returns a []string containing its words. Describe the result as a slice: a list-like collection whose elements can be accessed with numbered bracket indices.
- Length and zero-based indices. Explain that the built-in len function gives the number of elements in a collection such as a slice, and can also measure strings and maps. Distinguish that count from zero-based indices: the last index is len(yourVariable) - 1.
- File input for word counting. Explain why the word-counting program needs to load text from a file and identify the os package as the next thing to explore.
- Filesystem access with os. Explain that the os package provides cross-platform access to the filesystem and that os.ReadFile reads a file’s contents into memory.
- Multiple return values and errors. Explain that a Go function can return multiple values and that os.ReadFile returns the file contents as a byte slice followed by an error indicating whether a problem occurred opening the file.
- Nil error checks. Explain that Go code checks whether an error value is nil and handles a non-nil error as soon as possible.
- Logging and unsuccessful exit. Describe the simple-program response to an error: use log to print a timestamped message to standard error rather than normal output, then call os.Exit(1) to terminate immediately with a code indicating an unexpected failure.
- Word counting from file contents. Trace how the program converts the bytes returned by os.ReadFile with string(fileContents), uses strings.Fields to split the text into nonempty whitespace-separated chunks, and counts the resulting words.
- Running with the expected file. Describe how to run this version with go run: create words.txt in the same directory as main.go because the program currently uses that hardcoded filename, then try different file contents.
- Program arguments for file selection. Explain that the program currently reads a fixed file, words.txt, and that reading any file requires processing program arguments using the os package.
- Command-line argument positions. Explain that os.Args holds the arguments passed when a Go program starts, with the command itself at index 0 and the first supplied argument at index 1. Describe using os.Args[1] as the filename to load.
- Safe command-line argument access. Explain why accessing os.Args[1] without a check can panic, and describe using len(os.Args) to check that an argument exists before accessing it. Apply the same length-checking rule before accessing an arbitrary slice element.
- File argument trial. Describe trying the program with a text file supplied as an argument, including the option of using its own source file, and comparing its result with wc if wc is installed.
- Whole file memory cost. Explain why os.ReadFile is adequate for smaller files but less suitable when the program must handle files of any size or many files.
- Buffered text scanning. Describe how bufio buffering and its Scanner type let the program receive split-up text one piece at a time, while leaving the splitting details to the package.
- Exploring types with go doc. Explain how to use go doc to inspect a package type, its methods, and its examples. Describe how its output presents the type definition and documentation, then functions such as NewScanner that return that type.
- Scanner token and loop behavior. Describe how Scanner produces tokens and how Scan, Text, and Err work together in a loop. State that its default tokens are lines without trailing newlines, though its splitting behavior can be changed.
- Linewise word count adaptation. Explain how to adapt the Scanner example to count words: apply strings.Fields to each scanned line, add each line's count to a total outside the loop, and check Err afterward. Contrast this with loading the whole file at once using os.ReadFile.
- Files as scanner readers. Explain that bufio.NewScanner accepts an io.Reader, meaning any type with a Read method that takes a byte slice and returns an integer and an error. Describe how os.Open provides an os.File whose Read method meets that requirement.
- I/O interface conventions. Describe Reader, Writer, Seeker, and Closer as small interfaces for reading, writing, moving through data, and closing it, with combined interfaces such as ReadCloser. Explain that these interfaces do not perform I/O themselves; they specify capabilities that functions can request and custom types can provide.
- Scanner loop integration. Describe replacing os.Readfile with os.Open, passing the opened file to a scanner, and starting the scanner loop. Explain that the program functions as before but can now handle files no matter how large they are.
- Function types and scanner behavior. Explain that SplitFunc is a function type: a variable of that type can hold and call any function with the required inputs and outputs. Describe how supplying a different split function changes a Scanner’s tokenization while retaining the Scanner loop.
- Word counting with ScanWords. Describe how the bufio-provided ScanWords function makes a Scanner produce words rather than lines, so the program counts words by adding one on each iteration of the scanner loop.
- Multiple file argument loop. Describe how to process any number of files by looping over os.Args[1:], assigning each argument to filename, and applying the existing file-processing operation to each one.
- Range index and value forms. Explain that range automatically stops after visiting every item, and distinguish its one-variable index form from its two-variable index-and-value form. Describe using _ to discard the index when only the value is needed.
Section by section
Introduction
The chapter introduces a hands-on Go project that uses a real-world-style utility to teach language fundamentals and introduce Go’s tooling.
- Hands-on utility project: Describe how the chapter teaches Go by building one piece of software modeled on a real-world utility, introducing source structure and language fundamentals along the way rather than presenting reference material alone.
Why it matters: The project gives the chapter’s language lessons a practical context. - Go tooling ecosystem: Explain that Go includes tools beyond its language and compiler, and that the chapter encourages using those tools early to build a well-rounded foundation.
Why it matters: Using Go’s tools is part of learning how to work effectively in its ecosystem.
Key terms: Go source structure, Go language fundamentals, ecosystem of tools, format your code, manage dependencies, test code, research documentation
2.1 Goal: A program to count words
The section introduces a Go word-counting project by proposing to recreate the familiar `wc` command-line tool.
- Go word-counting project: Explain that the chapter will build a Go program to count words in text files, rather than simply use the existing `wc` command-line tool, which counts lines and words.
Why it matters: This project gives the chapter a practical task through which to introduce Go.
Key terms: wc, word count, text files
2.2 What you need first
This section encourages readers to code along and outlines the minimum tools and optional setup guidance for doing so.
- Hands-on Go setup: Explain why the book encourages coding along, distinguish the minimum setup from tools that may improve the experience, and state where to find current setup recommendations.
Why it matters: A working development environment lets you practice Go as you read.
Key terms: Go installation, built-in terminal, IDE, alternative terminal emulator, book’s website
2.2.1 A code editor
Choose a comfortable code editor for Go, add Go support if needed, and ensure the Go tools remain available from the command line.
- Editor choice and Go support: Explain that any comfortable editor can be used for Go, but features such as autocompletion and documentation may require plugins or extensions. Describe where to look for editor recommendations or setup instructions.
Why it matters: You can use a familiar editor without assuming every editor provides the same Go features. - Command-line Go tool access: Explain that even if an editor installs Go, the Go tools must still be accessible from the command line because the book uses them regularly. State that installing Go for the operating system is a safe way to ensure that access.
Why it matters: Later exercises rely on running Go tools outside the editor.
Key terms: Go Wiki, editor plugins, IDEs, autocompletion, Go tools, command line
2.2.2 Git
This section explains why Git is useful for Go development and how to get it if it is not already installed.
- Git as a Go prerequisite: Explain that Git is a revision control system and that the Go toolchain uses it by default to fetch dependencies from repositories such as GitHub. State that, if an editor has not installed Git, you can download and install it from the Git website.
Why it matters: Having Git available makes it easier to fetch third-party libraries when your project needs more than the Go standard library.
Key terms: Git, revision control system, Go toolchain, dependencies, Go standard library
2.2.3 The Go toolchain
This subsection tells readers where to install Go and what the installation provides for building and testing programs.
- Go toolchain installation: Describe how to install Go from the official installation page and name what the distribution includes: the standard library, compiler, and command-line tools for building and testing software.
Why it matters: You need the Go distribution before you can build or run the chapter’s program.
Key terms: Go distribution, standard library, compiler, command-line tools
2.2.4 Terminal access
This section explains where to type the commands used throughout the Go project.
- Terminal access options: Describe a terminal as the interface for typing commands and identify where to find one: in an IDE or through the operating system. Mention that other terminal applications are optional if the built-in one feels bare-bones.
Why it matters: You need a place to enter the commands used to create and run the project.
Key terms: terminal emulator, command prompt, terminal
2.3 Creating your project root
This section introduces the two steps for setting up a new Go project: creating a code directory and initializing it as a Go module.
- Project root setup steps: State that setting up a Go project starts by creating a directory for the code, then initializing that directory as a Go module.
Why it matters: These two steps establish where the project’s code will live and that the directory is a Go module.
Key terms: directory, Go module
2.3.1 Creating a directory
Create a project root directory for the word-count program and understand why Go projects need one.
- Wordcount project root: Explain that the word-count project starts with a directory named wordcount, which serves as the project root where its code lives and helps Go find and build it. State that a containing gobook directory is optional.
Why it matters: The project root gives the program’s code a clear home before you add files or initialize the module.
Key terms: project root, wordcount, gobook
2.3.2 Initializing the project module
This section initializes the wordcount project as a Go module, explains the resulting go.mod file, and gives the reason modules replaced the older GOPATH workflow.
- Wordcount module initialization: Describe running `go mod init gobook/wordcount` in the `wordcount` directory to initialize the project. Explain that `mod` is a subcommand of the `go` tool and that the module name’s final path segment matches the directory name.
Why it matters: Initializing the module prepares the directory for the Go code you write next. - Go module file contents: Explain that initialization creates `go.mod` in the project’s main module directory and that the Go toolchain refers to it when building code there or beneath it. Describe the `module` directive as the module name and the `go` directive as the Go version used for the software.
Why it matters: The go.mod file identifies the project to the toolchain and records its module name and Go version. - Modules versus GOPATH: Explain that modules name projects and manage dependency versions so people working from the source can use the same dependencies, without keeping every project under GOPATH. Contrast this with the older GOPATH layout and versioning difficulties, and mention that Go’s package and checksum databases help keep public dependencies available.
Why it matters: This explains why initializing a module is useful rather than just an extra setup step.
Key terms: Go modules, go command, go mod init, module name, go.mod, main module, module directive, go directive, Go toolchain, dependencies
2.4 The first iteration: Counting spaces
This section introduces a simple space counter as the first version of a word-counting program that the chapter will improve.
- Initial space counting approach: Explain that the first version counts spaces in a string as a simple starting point for counting words, and that later iterations will improve it.
Why it matters: This simple counter gives you a baseline for understanding why the program changes later.
Key terms: spaces, words, first pass
2.4.1 Creating main.go
This subsection creates a first complete Go program in main.go that counts spaces in a text variable.
- First space-counting program: Describe creating main.go and explain that the program examines each character in a text variable to count spaces, with rough edges to improve later.
Why it matters: This program is the starting point for running and improving the word counter.
Key terms: main.go, text variable, spaces
2.4.2 Building the program with go build
This section shows how to build the first Go program into a runnable executable.
- Building a Go executable: Explain how to run go build from the directory containing main.go and what it produces: it compiles all .go files in that directory into an executable named after the directory by default.
Why it matters: This lets you turn the word-count source code into a command you can run.
Key terms: go build, main.go, executable, wordcount
2.4.3 Running the program with go run
This subsection introduces go run as a quick way to run the word-counting program without first building it separately.
- Running with go run: Explain that you can use go run from the project directory to run the program without first invoking go build, making it useful for quick tests as the program changes.
Why it matters: It gives you a quick way to try each change to the program.
Key terms: go run, go build, go tool
2.4.4 Formatting your code with gofmt
This section introduces gofmt as Go’s standard formatter and shows how to check and apply its formatting.
- Purpose of standard formatting: Explain that gofmt, bundled with Go, gives Go code a consistent appearance regardless of individual style preferences. Describe how this reduces style debates and makes unfamiliar code easier to read.
Why it matters: A shared format lets developers focus on the code rather than formatting choices. - Checking and applying gofmt: Describe using gofmt -d on main.go to display differences from standard formatting, with no output indicating that the file is already formatted. State that gofmt -w modifies the file in place, though an editor may run the formatter automatically on save.
Why it matters: These commands let you check formatting or fix it when your editor has not done so.
Key terms: gofmt, standard formatting, gofmt -d, diff, gofmt -w
2.4.5 Package declarations
This section explains how package declarations organize Go code and why package main, together with a main function, produces a runnable program.
- Packages and module boundaries: Explain that packages are Go’s basic unit of code organization: they separate concerns and determine which functions, types, and values are visible elsewhere. A module can contain multiple packages, which can be imported separately.
Why it matters: Package boundaries help you understand how Go code is organized and shared. - Executable package and entry point: Describe how declaring package main marks the files as an executable program, and how Go looks across the main-package files in a directory for the main function that starts the program. State that both are needed to produce a command-line executable.
Why it matters: This explains why the example program needs both its package declaration and its starting function.
Key terms: packages, module, imported, package main, main function, executable
2.4.6 Import declarations
This section explains how import declarations identify packages a Go program depends on, where they belong, and why the build toolchain needs them.
- Import purpose and placement: Explain that an import declaration names packages used by the current package so the Go build toolchain can find and build needed dependencies, and that imports come directly after the package declaration.
Why it matters: Imports tell the toolchain what other code your program needs before it builds your code. - Single and multiple imports: Describe the section's `fmt` example as a single imported package for formatted and screen output, and state that imports can be written individually or grouped in a block.
Why it matters: The `fmt` example shows what an imported package provides, while import blocks accommodate more dependencies.
Key terms: import declaration, package declaration, dependencies, build toolchain, fmt package, formatted output, import declarations, blocks of multiple imports
2.4.7 Blocks and scope
This section explains where a Go program starts and how nested blocks determine which names code can use.
- Main function execution: Explain that main is the program’s entry point: execution starts there when the program runs, and returning from main ends the program. Describe that this main function takes no arguments and returns no values.
Why it matters: This tells you where to start tracing what a Go program does. - Block visibility rules: Explain that braces delimit blocks such as functions and loops, and that a name declared in a block is visible after its declaration within that block and in inner blocks—not in unrelated blocks or before its declaration.
Why it matters: These rules explain why code in one part of a program can use a name while code elsewhere cannot. - Package and universe scopes: Distinguish the package block, whose package-level declarations are visible throughout the package, from the universe block, which contains Go’s built-in functions, types, and constants. Explain that an inner scope can see names in its surrounding scopes.
Why it matters: This accounts for names a function can use even though they were not declared inside it.
Key terms: main, entry point, block, scope, visibility, identifier, package block, universe block, append, built-in types
2.4.8 Variable declaration
This section explains explicit and short variable declarations, zero values, type inference, and how short declarations can shadow variables in a new scope.
- Explicit declarations and zero values: Explain how var declares a variable with an explicit type and how an uninitialized variable receives a zero value, such as 0 for an int.
Why it matters: Zero values explain why a newly declared counter can be used immediately. - Short declarations and type inference: Describe how := declares and assigns a new variable in one step, with Go inferring its type from the assigned value.
Why it matters: Short declarations let you introduce variables without a separate declaration or an explicit type. - Shadowing across block scopes: Explain how using := inside an if block can create a new variable with the same name as an outer variable, leaving the outer value unchanged after the block.
Why it matters: Recognizing shadowing helps explain why a value set inside a block can appear to disappear afterward.
Key terms: var, int, zero value, :=, short declaration operator, type inference, short declaration, scope, shadowing, serverAddress
2.4.9 Comments
This subsection introduces Go’s comment syntax and explains why clear comment writing matters for documentation.
- Go comment styles and documentation: Describe Go’s two comment styles and explain why comments intended as documentation should use complete sentences and proper punctuation.
Why it matters: Go documentation is written in comments, so clear comments can help other developers understand your code.
Key terms: line comments, block comments, Go documentation
2.4.10 Basic for loops
Introduces Go’s three-part for loop and uses it to count spaces in a string.
- Go’s unified loop statement: Explain that Go uses the for statement for looping rather than separate constructs such as while and do..while, and that the three-part loop shown here is one form of for.
Why it matters: This helps you recognize Go loops even when they serve purposes handled by different keywords in other languages. - Three-part loop execution: Describe the init, condition, and post parts of the three-part for loop: init runs once, the Boolean condition is checked before each iteration, and post runs after each iteration. State that init and post are optional.
Why it matters: The order of these parts determines when a loop starts, continues, and advances. - Space-counting loop example: Trace how the example uses i to track a position in the string, checks the character at that position against a space, and increments numSpaces when they match as it proceeds through the string.
Why it matters: The example connects loop control to the word-counting program’s first counting strategy.
Key terms: for statement, three-part for loop, init statement, condition, post statement, for loop, i, numSpaces, space character, numSpaces++
2.4.11 Calling functions in other packages
Shows how to print the word-count result with a function from another package and explains which package names other code can access.
- Cross-package function calls: Explain how `fmt.Println` calls `Println` from the `fmt` package and prints its arguments in order, separated by spaces.
Why it matters: This is how the program displays the result it has counted. - Uppercase exported names: Explain that a Go identifier beginning with an uppercase letter is exported and can be accessed by code that imports its package, without a separate export declaration.
Why it matters: This rule explains why the program can call `Println` and other standard-library functions.
Key terms: fmt, fmt.Println, package.function, Println, identifier, exported, uppercase letter
2.5 A better way to split the string
The section shows why counting spaces gives unreliable word counts and introduces strings.Fields as a simpler way to split text into words.
- Reliable word counting approach: Explain why counting spaces can produce the wrong word count when text has extra spaces, and describe using strings.Fields to split the text into words instead of counting spaces in a loop.
Why it matters: The change fixes a flaw in the first word-counting approach while simplifying the code.
Key terms: counting spaces, strings.Fields
2.5.1 Introducing package strings
Introduces the standard library strings package as a tool for manipulating strings, in contrast to fmt’s focus on printing and formatting them.
- Strings package purpose and uses: Explain that the standard library strings package manipulates strings, unlike fmt, which primarily prints and formats them. Describe uses such as changing case, building strings piece by piece, and splitting them.
Why it matters: This distinction helps you choose the right package for the next version of the word-counting program.
Key terms: strings package, fmt package, standard library, splitting
2.5.2 Using go doc to view package documentation
This section shows how to look up Go package documentation and why strings.Fields is useful for counting words.
- Finding package documentation: Explain how to use go doc to read package documentation, what it shows by default, and what the -all flag adds. Describe pkg.go.dev as an online alternative.
Why it matters: Looking up package documentation helps you find and use Go’s existing functions. - Standard library word splitting: Justify using an existing standard library function for a common task: strings.Fields handles extra spaces when splitting text, avoiding the need to handle them yourself.
Why it matters: A standard library function can save time and avoid edge cases in the word counter.
Key terms: go doc, -all, documentation comments, pkg.go.dev, standard library, strings.Fields
2.5.3 Multiple imports in an import directive
This section shows how to import both fmt and strings in one directive and describes common import-grouping conventions.
- Multiple package import directive: Explain how to add strings alongside fmt so the program can use strings.Fields: either write a second import directive or put both package paths inside one parenthesized import directive.
Why it matters: The word-counting program needs to import strings before it can call strings.Fields. - Import grouping conventions: Describe the common convention of listing standard-library imports first, then a blank line before other dependencies, and note that editors may arrange imports automatically on save.
Why it matters: Recognizing the convention makes automatically rearranged imports less surprising.
Key terms: import directive, strings package, strings.Fields, standard library imports, other dependencies
2.5.4 Slices and len
This section uses strings.Fields to produce a slice of words and len to count them, while introducing slice indexing and its bounds.
- Fields result as a slice: Explain that strings.Fields takes one string and returns a []string containing its words. Describe the result as a slice: a list-like collection whose elements can be accessed with numbered bracket indices.
Why it matters: The word counter counts the elements returned by Fields rather than counting spaces. - Length and zero-based indices: Explain that the built-in len function gives the number of elements in a collection such as a slice, and can also measure strings and maps. Distinguish that count from zero-based indices: the last index is len(yourVariable) - 1.
Why it matters: This lets you print a word count and avoid going past the end when indexing in a loop.
Key terms: strings.Fields, []string, slice, index, len, zero-based indices, len(yourVariable) - 1
2.6 Reading from a file
This section introduces file input as the next step for a word-counting program that currently requires source edits to count a new string.
- File input for word counting: Explain why the word-counting program needs to load text from a file and identify the os package as the next thing to explore.
Why it matters: File input lets the program count new text without changing its source code each time.
Key terms: os package, load from a file
2.6.1 Using the os package to interact with the filesystem
Introduces the os package as a cross-platform way to access the filesystem and identifies os.ReadFile as a quick way to load a file’s contents into memory.
- Filesystem access with os: Explain that the os package provides cross-platform access to the filesystem and that os.ReadFile reads a file’s contents into memory.
Why it matters: This is the first step toward counting words in a file rather than in a string already in the program.
Key terms: os package, filesystem, os.ReadFile
2.6.2 Multiple return values
This subsection introduces Go functions that return multiple values, using os.ReadFile to show the common result-then-error pattern.
- Multiple return values and errors: Explain that a Go function can return multiple values and that os.ReadFile returns the file contents as a byte slice followed by an error indicating whether a problem occurred opening the file.
Why it matters: Recognizing the result and error separately prepares you to handle file-reading failures.
Key terms: multiple return values, os.ReadFile, slice of bytes, error
2.6.3 Basic error handling and the log package
The section introduces Go’s basic error-handling pattern: check an error value, then log and exit if an error occurred.
- Nil error checks: Explain that Go code checks whether an error value is nil and handles a non-nil error as soon as possible.
Why it matters: This check determines whether the program can continue or needs to handle a failure. - Logging and unsuccessful exit: Describe the simple-program response to an error: use log to print a timestamped message to standard error rather than normal output, then call os.Exit(1) to terminate immediately with a code indicating an unexpected failure.
Why it matters: It makes failures visible separately from normal output and signals that the program did not finish successfully.
Key terms: error value, nil, log, standard error, timestamp, os.Exit(1), exit code
2.6.4 Counting the words in the loaded file
This section shows how to count words in a loaded text file by converting its raw bytes to a string and splitting it on whitespace.
- Word counting from file contents: Trace how the program converts the bytes returned by os.ReadFile with string(fileContents), uses strings.Fields to split the text into nonempty whitespace-separated chunks, and counts the resulting words.
Why it matters: This connects reading a file to the program’s word count. - Raw bytes and text choice: Explain why os.ReadFile returns raw bytes rather than assuming a file contains text, and describe bytes.Fields as an alternative that splits the byte slice without converting it to a string.
Why it matters: It clarifies why the example converts the contents and when a byte-oriented alternative is available.
Key terms: os.ReadFile, slice of bytes, string(fileContents), strings.Fields, bytes package, bytes.Fields
2.6.5 Running the program on a file
This section shows how to run the word-count program with its expected input file and how to check its behavior when that file is missing.
- Running with the expected file: Describe how to run this version with go run: create words.txt in the same directory as main.go because the program currently uses that hardcoded filename, then try different file contents.
Why it matters: The program needs the file it was written to load before you can try it on different text. - Missing file error check: Explain how removing words.txt and running the program again tests its error handling when the expected input file is unavailable.
Why it matters: Trying the missing-file case checks what the program does when it cannot load its input.
Key terms: go run, words.txt, main.go, hardcode, error handling
2.7 Specifying the file
This section introduces the move from reading a fixed file to choosing a file through program arguments.
- Program arguments for file selection: Explain that the program currently reads a fixed file, words.txt, and that reading any file requires processing program arguments using the os package.
Why it matters: Program arguments let the word counter work with files other than words.txt.
Key terms: program arguments, os package, words.txt
2.7.1 Getting program arguments from os.Args
This section shows how to get a filename supplied on the command line from Go’s os.Args.
- Command-line argument positions: Explain that os.Args holds the arguments passed when a Go program starts, with the command itself at index 0 and the first supplied argument at index 1. Describe using os.Args[1] as the filename to load.
Why it matters: The position of each argument determines which value the program uses as its filename.
Key terms: os package, os.Args, command, first argument
2.7.2 Avoiding a panic by using len
Shows how to prevent a panic when a program is run without a file-name argument.
- Safe command-line argument access: Explain why accessing os.Args[1] without a check can panic, and describe using len(os.Args) to check that an argument exists before accessing it. Apply the same length-checking rule before accessing an arbitrary slice element.
Why it matters: A length check lets the program handle a missing file name instead of crashing.
Key terms: os.Args, len, panic, slice
2.7.3 Running your program with arguments
This section invites readers to run the word-count program on a chosen text file and optionally compare its result with wc.
- File argument trial: Describe trying the program with a text file supplied as an argument, including the option of using its own source file, and comparing its result with wc if wc is installed.
Why it matters: Trying different files checks that the program can work with a file you choose.
Key terms: wordcount, wc, arguments
2.8 A more efficient method
Introduces buffered scanning as a way to count words without loading each entire file into memory.
- Whole file memory cost: Explain why os.ReadFile is adequate for smaller files but less suitable when the program must handle files of any size or many files.
Why it matters: The memory cost explains why the word-counting program needs a different way to read files. - Buffered text scanning: Describe how bufio buffering and its Scanner type let the program receive split-up text one piece at a time, while leaving the splitting details to the package.
Why it matters: Scanner lets the program focus on processing text and checking errors rather than implementing chunked reading itself.
Key terms: os.ReadFile, memory, bufio, Scanner, buffering
2.8.1 Using go doc to learn more about types
The section uses go doc to understand bufio.Scanner, then adapts its scanning loop to count words one line at a time.
- Exploring types with go doc: Explain how to use go doc to inspect a package type, its methods, and its examples. Describe how its output presents the type definition and documentation, then functions such as NewScanner that return that type.
Why it matters: Documentation and examples help you work out how an unfamiliar Go type is meant to be used. - Scanner token and loop behavior: Describe how Scanner produces tokens and how Scan, Text, and Err work together in a loop. State that its default tokens are lines without trailing newlines, though its splitting behavior can be changed.
Why it matters: The scanning loop makes it possible to process input piece by piece and check for errors afterward. - Linewise word count adaptation: Explain how to adapt the Scanner example to count words: apply strings.Fields to each scanned line, add each line's count to a total outside the loop, and check Err afterward. Contrast this with loading the whole file at once using os.ReadFile.
Why it matters: This connects the documented scanning pattern to the program's word-counting task while processing input incrementally.
Key terms: go doc, go doc -all, go doc -ex, NewScanner, Scanner, tokens, Scan, Text, Err, SplitFunc
2.8.2 Files and I/O interfaces
This section shows how an opened file can satisfy the scanner’s io.Reader requirement and explains why Go’s small I/O interfaces are useful conventions.
- Files as scanner readers: Explain that bufio.NewScanner accepts an io.Reader, meaning any type with a Read method that takes a byte slice and returns an integer and an error. Describe how os.Open provides an os.File whose Read method meets that requirement.
Why it matters: This connection lets you scan a file without converting the whole file into a string first. - I/O interface conventions: Describe Reader, Writer, Seeker, and Closer as small interfaces for reading, writing, moving through data, and closing it, with combined interfaces such as ReadCloser. Explain that these interfaces do not perform I/O themselves; they specify capabilities that functions can request and custom types can provide.
Why it matters: Recognizing these interfaces helps you tell what I/O operations a function can perform and what your own types must support.
Key terms: io.Reader, Read, os.File, os.Open, NewScanner, io package, Reader, Writer, Seeker, Closer
2.8.3 Integrating the scanner loop
This subsection integrates a scanner loop into the word-counting program so it retains its behavior while handling files of any size.
- Scanner loop integration: Describe replacing os.Readfile with os.Open, passing the opened file to a scanner, and starting the scanner loop. Explain that the program functions as before but can now handle files no matter how large they are.
Why it matters: The change removes the file-size limitation without changing what the program does.
Key terms: os.Readfile, os.Open, scanner, scanner loop
2.8.4 Function types for modifying behavior
The section shows how a function type lets bufio.Scanner change its tokenization behavior, making word counting a simple loop, and encourages using Go documentation to find such features.
- Function types and scanner behavior: Explain that SplitFunc is a function type: a variable of that type can hold and call any function with the required inputs and outputs. Describe how supplying a different split function changes a Scanner’s tokenization while retaining the Scanner loop.
Why it matters: It explains how the same Scanner can be adapted to different kinds of input tokens. - Word counting with ScanWords: Describe how the bufio-provided ScanWords function makes a Scanner produce words rather than lines, so the program counts words by adding one on each iteration of the scanner loop.
Why it matters: It turns the word-counting task into counting the tokens the Scanner already produces. - Documentation as a discovery tool: Explain why checking documentation for an unfamiliar Go package can reveal simpler or more flexible approaches, as the Scanner split functions did here, and identify go doc or pkg.go.dev as ways to read it.
Why it matters: Documentation can reveal useful package features before you build a more complicated solution yourself.
Key terms: SplitFunc, function type, bufio.Scanner, bufio.ScanWords, Scanner, tokens, strings.Fields, go doc, pkg.go.dev, documentation
2.9 Reading multiple files
The section extends the word-counting program to process multiple files by looping over command-line arguments and explains how Go’s range loop supplies indexes and values.
- Multiple file argument loop: Describe how to process any number of files by looping over os.Args[1:], assigning each argument to filename, and applying the existing file-processing operation to each one.
Why it matters: This turns the single-file program into one that can handle multiple files in a single run. - Range index and value forms: Explain that range automatically stops after visiting every item, and distinguish its one-variable index form from its two-variable index-and-value form. Describe using _ to discard the index when only the value is needed.
Why it matters: Choosing the right range form lets you retrieve filenames without manually tracking collection bounds.
Key terms: os.Args[1:], slice expression, range, filename, index, blank identifier, _
2.10 Summary
The chapter summary characterizes Go as a simple, compiled, statically typed language and highlights its documentation and standard library.
Where this is used later
- Modules (chapter 11) · The discussion of modules builds on the idea of fetching dependencies for a Go project.
- Running tests with go test (chapter 8) · Uses the installed tooling to run tests.
- Test-driven design: A calculator with a twist (chapter 8) · Initializing a Go module with go mod init.
- Versioning modules (chapter 11) · Module versioning is examined in more detail.
- Test functions (chapter 8) · Go package declarations are introduced.
- Black-box testing (chapter 8) · package declarations
- Integer types (chapter 3) · Earlier variable declarations using var and :=.
- Floating-point number types (chapter 3) · Variable declarations, including var and short declarations, were introduced earlier.
Concepts explained in this chapter
- io.Readerio.Reader is the Go interface for reading a stream of bytes into a supplied buffer.
- SplitFuncSplitFunc is the function type used by bufio.Scanner to define how input is divided into tokens.
- loopA for loop is a Go control-flow construct that repeatedly executes a block according to optional initialization and post clauses and a loop condition or range clause.
- bufio.Scannerbufio.Scanner is a Go type that incrementally reads input and returns successive tokens according to a configured split function.
- go docgo doc is the standard Go command that extracts and presents documentation from Go source code.
- go moduleA Go module is a versioned collection of Go packages described by a go.mod file and managed as a unit for dependency resolution and builds.
- tokenA token is a piece of input data produced by a scanner according to its splitting rules.
- go.mod fileA go.mod file is a Go module definition file that records the module path, Go language version, and module dependencies.
- go mod initgo mod init is a Go command that creates a go.mod file and initializes a module in a project directory.
- stringA string is Go's immutable sequence-of-bytes type, conventionally used to hold encoded text.
- 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.
- ioio is the Go standard-library package that defines interfaces and utilities for input and output operations.
- 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.
- GoGo is an open-source, statically typed programming language designed for efficient compilation, execution, concurrency, and software development tooling.
- zero valueA zero value is the default value assigned to a newly declared Go variable when no explicit initial value is provided.
- gofmtgofmt is the standard Go source-code formatter that applies the official Go formatting style.
- packageA package is a Go compilation and namespace unit that groups related declarations and controls their visibility across source files.
- fmtThe fmt package is a Go standard-library package that provides formatted input and output operations.
- error valueAn error value is a Go value of the error type that reports whether an operation encountered a problem.
- 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.
- os packageThe os package is a Go standard-library package that provides platform-independent interfaces for operating-system functionality, including files, processes, and environment variables.
- bufferingBuffering is the technique of temporarily accumulating input or output data in memory to reduce the cost of individual transfers.
- bufiobufio is Go's standard library package for buffered input and output, including scanner-based tokenization.
- go rungo run is the standard Go command that compiles and executes a Go program in one operation.
- entry pointThe main function is the specially named Go function that serves as the starting point of an executable program.
- main packageThe main package is the special Go package whose compiled program is intended to be an independently runnable executable.
- fmt.Printlnfmt.Println is a Go standard-library function that prints its arguments with spaces between them and a trailing newline.
- panicA panic is Go's runtime mechanism for terminating ordinary execution and reporting an unrecoverable run-time error unless recovered.
- command-line argumentA command-line argument is a value supplied to a program when it is invoked from a command line or operating-system process launcher.
- ReadCloserReadCloser is a Go interface combining the ability to read data with the ability to close a resource.
- io.Writerio.Writer is the Go interface for writing byte data to a destination.
- integerAn integer type represents whole-number values without a fractional component.
- byte typeA byte is an eight-bit unsigned integer value used in Go to represent raw binary data.
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.
