Searching...
English
EnglishEnglish
EspañolSpanish
简体中文Chinese
FrançaisFrench
DeutschGerman
日本語Japanese
PortuguêsPortuguese
ItalianoItalian
한국어Korean
РусскийRussian
NederlandsDutch
العربيةArabic
PolskiPolish
हिन्दीHindi
Tiếng ViệtVietnamese
SvenskaSwedish
ΕλληνικάGreek
TürkçeTurkish
ไทยThai
ČeštinaCzech
RomânăRomanian
MagyarHungarian
УкраїнськаUkrainian
Bahasa IndonesiaIndonesian
DanskDanish
SuomiFinnish
БългарскиBulgarian
עבריתHebrew
NorskNorwegian
HrvatskiCroatian
CatalàCatalan
SlovenčinaSlovak
LietuviųLithuanian
SlovenščinaSlovenian
СрпскиSerbian
EestiEstonian
LatviešuLatvian
فارسیPersian
മലയാളംMalayalam
தமிழ்Tamil
اردوUrdu
The Go Programming Language

The Go Programming Language

by Alan A.A. Donovan 2015 380 pages
4.44
1k+ ratings
Listen
Try Full Access for 7 Days
Unlock listening & more!
Continue

Key Takeaways

1. Go: A Language Built for Simplicity, Efficiency, and Reliability

Go was conceived in September 2007 by Robert Griesemer, Rob Pike, and Ken Thompson, all at Google, and was announced in November 2009.

Designed for modern challenges. Go emerged from frustration with the complexity of large software systems at Google. Its creators aimed for a language that was expressive, compiled quickly, executed efficiently, and was effective for building reliable and robust programs, especially networked servers and tools.

Simplicity is key. Go achieves its goals by borrowing good ideas from other languages while deliberately avoiding features that often lead to complexity and unreliable code. This includes omitting features like implicit numeric conversions, constructors/destructors, operator overloading, inheritance, generics (in the original design), exceptions (for routine errors), and macros.

Practical and performant. Go is a compiled language producing efficient machine code, designed to cooperate naturally with modern operating systems. It includes automatic memory management (garbage collection) and finds use across diverse domains, balancing expressiveness with safety and speed, often replacing untyped scripting languages.

2. Master the Fundamentals: Variables, Types, and Control Flow

A var declaration creates a variable of a particular type, attaches a name to it, and sets its initial value.

Basic building blocks. Go programs are built from fundamental constructs: variables store values, expressions combine values, basic types form aggregates, and statements control execution flow within functions. Understanding these basics is essential before tackling more advanced features.

Variables and types. Variables are declared using var or the short declaration := within functions. Every variable has a type (like int, string, bool) and is implicitly initialized to its zero value if not explicitly set. Go's type system is explicit, preventing many common errors at compile time.

Control flow. Go provides standard control flow statements: if for conditional execution, for for loops (the only loop construct, with various forms including range), and switch for multi-way branching. Parentheses are generally omitted around conditions, but braces are mandatory for bodies.

3. Organize Data with Powerful Composite Types: Slices, Maps, and Structs

Slices represent variable-length sequences whose elements all have the same type.

Aggregate data. Go provides composite types to group simpler values: arrays, slices, structs, and maps. Arrays are fixed-length sequences, rarely used directly due to their rigidity.

Dynamic sequences. Slices are built on top of arrays but offer dynamic sizing. They are lightweight structures with a pointer to an underlying array, a length, and a capacity. The built-in append function handles growth, potentially allocating a new underlying array and copying elements. Slices are not comparable with ==.

Key-value collections. Maps are unordered collections of key-value pairs, providing efficient lookups, insertions, and deletions. Keys must be comparable types. Maps are references to hash tables and are not comparable with == (except to nil).

Grouped fields. Structs are aggregate types grouping zero or more named fields of potentially different types. They are comparable if all their fields are comparable. Struct embedding allows composing types by including one struct within another, promoting its fields and methods.

4. Functions are First-Class Citizens, Enabling Flexible Code

Functions are first-class values in Go: like other values, function values have types, and they may be assigned to variables or passed to or returned from functions.

Modular units. Functions encapsulate logic, making programs modular and reusable. A function declaration specifies parameters, optional results, and a body. Arguments are passed by value, meaning the function receives copies.

Multiple results. Go functions can return multiple values, commonly used to return a result and an error indicator (value, err := ...). This encourages explicit error handling.

Anonymous functions and closures. Function literals (anonymous functions) can be defined and used inline. They can access variables from their enclosing scope, forming closures. This allows functions to have state and enables powerful patterns like the forEachNode traversal example.

Deferred execution. The defer statement schedules a function call to be executed just before the surrounding function returns, regardless of the return path (normal return, panic). This is invaluable for ensuring cleanup operations like closing files or releasing locks.

5. Go's Unique Approach to OOP: Methods, Interfaces, and Composition

A method is declared with a variant of the ordinary function declaration in which an extra parameter appears before the function name.

Behavior on types. Methods are functions associated with a specific named type (the receiver). Go allows methods on almost any named type, not just structs. This enables adding behavior to basic types, slices, maps, etc.

Implicit contracts. Interfaces define a set of methods. A concrete type implicitly satisfies an interface if it possesses all the required methods, without needing explicit declaration. This "duck typing" approach allows defining new interfaces that existing types can satisfy without modification.

Polymorphism and decoupling. Interfaces enable polymorphism, allowing functions to operate on any value that satisfies the interface, regardless of its underlying concrete type (e.g., io.Writer). This decouples code, making it more flexible and testable.

Composition over inheritance. Go favors composition via struct embedding over traditional class inheritance. Embedding a type promotes its methods, allowing the outer type to "inherit" behavior. This provides a clear way to build complex behaviors from simpler ones.

6. Concurrency is Go's Superpower: Goroutines and Channels

In Go, each concurrently executing activity is called a goroutine.

Lightweight processes. Goroutines are Go's mechanism for concurrent execution. Created with the go keyword before a function call, they are much lighter weight than traditional OS threads, allowing programs to easily run thousands or millions simultaneously.

Communicating Sequential Processes (CSP). Go promotes a concurrency model where independent goroutines communicate by sending values over channels. This contrasts with sharing memory and requiring explicit locks.

Synchronized communication. Channels are typed conduits for sending and receiving values between goroutines. An unbuffered channel synchronizes sender and receiver: a send blocks until a receiver is ready, and vice versa.

Decoupled communication. Buffered channels have a queue, allowing sends to proceed without blocking until the buffer is full, and receives to proceed until the buffer is empty. This decouples goroutines, smoothing out variations in processing rates.

7. Handle Shared State Safely: Mutual Exclusion and Synchronization

A data race occurs whenever two goroutines access the same variable concurrently and at least one of the accesses is a write.

Concurrency hazards. While channels are preferred, shared memory is sometimes necessary. Concurrent access to shared variables, especially with writes, can lead to data races, causing unpredictable and incorrect program behavior.

Avoiding data races. Strategies include:

  • Confinement: Restricting a variable's access to a single goroutine.
  • Serial Confinement: Passing variable ownership between goroutines via channels.
  • Mutual Exclusion: Allowing multiple goroutines to access a variable, but only one at a time.

Mutexes for exclusive access. The sync.Mutex provides exclusive locking. A goroutine calls Lock() to enter a critical section and Unlock() to exit. defer mu.Unlock() is the idiomatic way to ensure the lock is released.

Read/Write locks. sync.RWMutex allows multiple readers or a single writer, improving performance when reads are frequent and writes are rare. RLock() and RUnlock() are used for read access.

Memory visibility. Synchronization primitives like channels and mutexes ensure that memory writes by one goroutine are visible to others, preventing unexpected behavior due to processor cache inconsistencies. sync.Once guarantees a function runs exactly once, safely initializing shared resources.

8. Organizing Code: Packages and the Go Tool

Each package defines a distinct name space that encloses its identifiers.

Modularity and encapsulation. Go code is organized into packages, serving as units of modularity, encapsulation, and separate compilation. Packages provide namespaces, preventing naming conflicts, and control visibility: capitalized identifiers are exported, uncapitalized are not.

Import paths. Packages are identified by unique import paths (e.g., "fmt", "golang.org/x/net/html"). These paths correspond to directory structures within a workspace.

The go tool. The go command is the central tool for managing Go source code. It acts as a package manager, build system, and test runner.

Workspace structure. The go tool relies on conventions, primarily the GOPATH environment variable, which points to a workspace root containing src, pkg, and bin directories. Source code lives under src following import paths.

Key go commands:

  • go get: Downloads, builds, and installs packages and dependencies.
  • go build: Compiles packages, creating executables for main packages.
  • go install: Compiles and installs packages and commands, caching results.
  • go run: Compiles and runs a main package from source files.
  • go test: Runs tests and benchmarks.
  • go doc: Displays documentation.

9. Embrace Explicit Error Handling for Robust Programs

A function for which failure is an expected behavior returns an additional result, conventionally the last one.

Errors as values. Go treats errors as ordinary values, typically returned as the last result of a function (value, err). A nil error indicates success; a non-nil error indicates failure.

The error interface. The built-in error type is an interface with a single method, Error() string. This allows different types to represent various kinds of errors while satisfying a common contract.

Handling strategies. Callers are responsible for checking errors and taking appropriate action:

  • Propagate: Return the error to the caller, often adding context (fmt.Errorf).
  • Retry: Attempt the operation again, possibly with back-off.
  • Log and Continue: Record the error and proceed with reduced functionality.
  • Log and Exit: Print the error and terminate the program (usually only in main).
  • Ignore: Deliberately disregard the error (rare, requires documentation).

Avoiding exceptions. Go's approach avoids exceptions for routine errors, promoting clearer control flow and preventing unexpected crashes or incomprehensible stack traces for end-users. Panic/Recover is reserved for truly exceptional, unrecoverable errors indicating a program bug.

10. Reflection: Inspecting and Manipulating Types at Runtime

Reflection is provided by the reflect package.

Runtime introspection. Reflection allows a program to examine and modify its own structure and data at runtime, without knowing the specific types at compile time. This is achieved through the reflect package.

Key types: Type and Value. reflect.Type represents a Go type, providing methods to query its kind, name, and structure (fields, methods, etc.). reflect.reflect.TypeOf(x) returns the Type of x's dynamic type. reflect.Value represents a value of any type, offering methods to inspect its kind and retrieve its underlying data (Int(), String(), Bool(), etc.). reflect.ValueOf(x) returns a Value for x.

Dynamic operations. Reflection enables operations like iterating over struct fields (NumField(), Field(i)), map keys/values (MapKeys(), MapIndex(key)), and slice/array elements (Len(), Index(i)).

Modifying values. An addressable reflect.Value (obtained from a variable, e.g., reflect.ValueOf(&x).Elem()) can be modified using Set(v) or type-specific setters like SetInt(i). CanSet() checks if a Value is modifiable.

Caution advised. Reflection is powerful but comes with costs:

  • Fragility: Runtime panics instead of compile-time errors for type mismatches or invalid operations.
  • Complexity: Code is harder to understand and reason about statically.
  • Performance: Reflection operations are significantly slower than direct code.

11. Leverage Built-in Tools for Testing, Benchmarking, and Profiling

Testing shows the presence, not the absence of bugs.

Integrated tooling. Go provides built-in support for automated testing, benchmarking, and profiling, integrated with the go test command. Test files end in _test.go and are separate from production code.

Test functions. Functions starting with Test (e.g., TestMyFeature) take a *testing.T parameter. Methods on *testing.T (Error, Errorf, Fatal, Fatalf) report failures without stopping execution (unless using Fatal). Table-driven tests are common for checking multiple inputs.

Benchmarking. Functions starting with Benchmark (e.g., BenchmarkMyOperation) take a *testing.B parameter. They measure performance by running code b.N times. go test -bench=. runs benchmarks. benchmem flag adds memory allocation stats.

Profiling. Tools help identify performance bottlenecks:

  • CPU profile: Where CPU time is spent.
  • Heap profile: Where memory is allocated.
  • Blocking profile: Where goroutines are blocked.
    go test -cpuprofile=cpu.out collects data, go tool pprof analyzes it.

Examples. Functions starting with Example provide documentation and are checked by go test if they include // Output: comments. godoc displays them and allows interactive execution.

Last updated:

Review Summary

4.44 out of 5
Average of 1k+ ratings from Goodreads and Amazon.

The Go Programming Language is highly praised as the definitive book for learning Go, drawing comparisons to K&R's C book. Readers appreciate its comprehensive coverage, clear explanations, and practical examples. Many consider it essential for serious Go programmers, praising its depth and attention to language idioms. Some criticism exists regarding its suitability for absolute beginners and potential wordiness in places. Overall, it's regarded as an excellent resource for experienced programmers looking to master Go, with particular praise for its treatment of concurrency and low-level programming concepts.

Your rating:
Be the first to rate!

About the Author

Alan A.A. Donovan is one of the authors of The Go Programming Language. While limited information is provided about him specifically, the book's co-author Brian Kernighan is well-known in the programming community. Kernighan co-authored the influential "The C Programming Language" book in 1978 and has since gained a reputation as an exceptional technical writer. His involvement in The Go Programming Language lends significant credibility to the work, given his deep understanding of programming languages and his ability to explain complex concepts clearly and concisely. The authors' expertise in language design and systems programming is evident throughout the book.

Listen to Summary
0:00
-0:00
1x
Dan
Andrew
Michelle
Lauren
Select Speed
1.0×
+
200 words per minute
Home
Library
Get App
Create a free account to unlock:
Requests: Request new book summaries
Bookmarks: Save your favorite books
History: Revisit books later
Recommendations: Personalized for you
Ratings: Rate books & see your ratings
100,000+ readers
Try Full Access for 7 Days
Listen, bookmark, and more
Compare Features Free Pro
📖 Read Summaries
All summaries are free to read in 40 languages
🎧 Listen to Summaries
Listen to unlimited summaries in 40 languages
❤️ Unlimited Bookmarks
Free users are limited to 10
📜 Unlimited History
Free users are limited to 10
📥 Unlimited Downloads
Free users are limited to 1
Risk-Free Timeline
Today: Get Instant Access
Listen to full summaries of 73,530 books. That's 12,000+ hours of audio!
Day 4: Trial Reminder
We'll send you a notification that your trial is ending soon.
Day 7: Your subscription begins
You'll be charged on May 21,
cancel anytime before.
Consume 2.8x More Books
2.8x more books Listening Reading
Our users love us
100,000+ readers
"...I can 10x the number of books I can read..."
"...exceptionally accurate, engaging, and beautifully presented..."
"...better than any amazon review when I'm making a book-buying decision..."
Save 62%
Yearly
$119.88 $44.99/year
$3.75/mo
Monthly
$9.99/mo
Try Free & Unlock
7 days free, then $44.99/year. Cancel anytime.
Scanner
Find a barcode to scan

Settings
General
Widget
Loading...