The Applied Go Weekly Newsletter logo

The Applied Go Weekly Newsletter

Subscribe
Archives
June 8, 2025

Own Your Errors! • The Applied Go Weekly Newsletter 2025-06-08

AppliedGoNewsletterHeader640.png

Your weekly source of Go news, tips, and projects

2025-06-08 Newsletter Badge.png

Own Your Errors!

Hi ,

It's over.

The discussions, the stream of proposals, the flame wars on social media about Go's error handling—or, more precisely, how it could and should be done better (where the definition of "better" always has been highly biased). They're all over now.

The Go team has decided to pull the plug. Go's error handling syntax will remain as it is now. Open proposals and discussions will be closed, new proposals and discussions will not be accepted.

To many, this might seem a hard decision. This is entirely understandable; many people have invested time and energy to redesign the error handling syntax to be less verbose and make the "happy path" more visible. Yet, none of the proposals convinced the team, as each has drawbacks (of different kinds). The whole discussion over all the years hasn't led anywhere.

You may or may not be happy with this decision, but it has two undeniable advantages: Clarity for the future and less noise in Go's GitHub issues.

Let's take error handling as it is and move on to more interesting topics.

May the err be with you.

–Christoph

Featured articles

[ On | No ] syntactic support for error handling - The Go Programming Language

Go's error handling will remain the way it always has been. Robert Griesemer explains the ins and outs and the backs and forths that preceded this decision, and why the team ultimately decided to push the brakes.

Let's Write a JSON Parser From Scratch - by Sushant Dhiman

Writing a parser may seem daunting if you haven't written one before. However, the smaller the language is, the easier is it to write a parser for it. Sushant Dhiman picked JSON for that reason. JSON is small and has few rules, and therefore greatly increases the chance that a parser project will actually get finished.

A JavaScript Developer's Guide to Go

Learning a new language is so much more than just memorizing a different syntax. On the other hand, if you already know a language, you can learn a new one faster by focusing on the differences between the two. Prateek Surana did this when transitioning from JavaScript to Go.

Podcast corner

No Silver Bullet: Event-Driven Architecture: The Hard Parts

There is quite some Go inside this episode!

Fallthrough: Building An Open Source Maintenance Company

The tragic of open source: Everyone wants to use it, no one wants to pay for it. ("It's open source after all...!?") Few companies are rigorously paying back to open source like Buttondown, my dear newsletter platform, does. What if there was a company that exclusively focuses on paying open source maintainers? Filippo Valsorda and Daniel McCarney talk to Angelica and Matt about Geomys, a company that does exactly this.

go podcast() 058: Starting in Go with Yann Bizeul

All paths lead to Rome, an ancient proverb says, and many paths lead to Go. Yann Bizeul talks to Dominic St-Pierre about his path to Go.

Cup o' Go: 🚫 Go team says no, what is HTTP error 407, and do you need DI frameworks

Go doesn't need a new error syntax and probably also no DI frameworks. It does need commit 4d1c255, though, and gophers who create sophisticated tools like git-bug and oapi-codegen.

Spotlight: Hard truths about Go's error handling

Now that it's clear that Go will not receive any fancy error handling syntax—no check, no try(), no ?—, one part of the Go community cheers in joy while the other part is genuinely disappointed.

I'm here for the disappointed ones. I hear you: the syntax is verbose to the point that error handling sometimes dominates the code on the screen. Yet, error handling design is more than just defining a syntax. When Go came out, it boldly derived from the mainstream languages' way of hiding the error handling from the eyes of the developer as much as possible. Instead, Go makes error handling explicit and clearly visible.

And don't forget:

If 80% of your Go code consists of error handling, it is because 80% of your code might fail at any time.

–Preslav Rachev

So here are a few hard truths about error handling in Go.

Hard truth #1: Errors are values

Errors in Go do not represent a special program state. They don't change execution flow under the hood as exceptions do. They are just values of type error. As such, an error can be: -

  • a function's return value (or a part of it)
  • a field in a struct
  • passed through channels
  • collected in a slice
  • (you name it)

When a function encounters an erroneous situation, it creates an error and returns it to the caller:

if (divisor == 0) {
  return errors.New("division by zero")
}

No hidden execution paths, no silent unwrapping of the function call chain until some exception handler chimes in. The direct caller gets informed of the error and must act upon it.

Hard truth #2: errors is an interface type

The error type is an interface with one method, Error():

type error interface {
  Error() string
}

The simplest implementation of an error is errorString:

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

That's it. No runtime magic, no specialized type. Just an interface, a struct, and a method. No baking, no boiling, no artificial flavors required.

Hard truth #3: You can create custom error types

The interface nature of errors make creating custom errors a breeze. Any custom type can represent an error, as long as it implements the Error() string method.

Hard truth #4: Errors can be wrapped with context info

When I wrote about an error handling proposal back in February, I shared a story of a low-level error that bubbled up from the depths of an application and left a single message in the log files: "Generic SSA NOTOK". Without context, it was impossible to trace the error message back to the situation that triggered it.

This is why proper error wrapping is important. Contextual information is gold when tracking down a malfunction.

Go, pragmatic as it is, uses string formatting for wrapping an error:

f, err := os.Open("nonexistent.file")
if err != nil {
  return fmt.Errorf("Cannot open nonexistent.file: %w", err)
}

Note the %w verb that must be matched by an argument that satisfies the error interface. Errorf() uses this for wrapping the passed-in error. (It is possible to pass multiple errors to Errorf() using multiple %ws, by the way.)

Hard truth #5: Better use errors.Is()/As() to compare errors

Because errors are values, you might be tempted to use the standard comparison operator == for comparing an error against another error, or some type assertion to test an error against a given error type, such as:

if err == ErrNotFound { ... }

if e, ok := err.(*FileError); ok { ... }

Beware! These attempts will fail with wrapped errors. They would only test the outermost error in a tree of wrapped errors.

Better use errors.Is() and errors.As().

errors.Is(err, target error) compares an error to a value, considering all errors in a tree of wrapped errors:

if errors.Is(err, ErrNotFound) { ... }

errors.As(err error, &target any) tests if the given error, or any error wrapped inside, is of a specific type. It does so by attempting an assignment to target, which must be a value of that type. If that assignment succeeds, target is set to the error value, so that the error handling code can access any fields and methods (if it has any).

if f, err := os.Open("nonexistent.file"); err != nil {
  var pathError *fs.PathError // 
  if errors.As(err, &pathError) {
    return fmt.Errorf("can't read file at '%s': %w", pathError.path, err)
  } else {
    return err
  }
}

Conclusion: Go's error handling is easy and straightforward, after all

Go's error handling is made of simple ingredients and is quite easy to understand and customize. If you're not convinced yet, or if you are curious to dig deeper, here is some more brain fodder:

  • Why Go's Error Handling is Awesome | rauljordan::blog
  • Popular Error Handling Techniques in Go - JetBrains Guide
  • Go's Error Handling Is a Form of Storytelling · Preslav Rachev
  • Go Error Handling Techniques: Exploring Sentinel Errors, Custom Types, and Client-Facing Errors | Arash Taher Blog
  • Go’s Error Handling Is Perfect, Actually :: Very Good Software, Not Virus
  • Working with Errors in Go 1.13 - The Go Programming Language
  • Errors are values - The Go Programming Language

Quote of the Week: Touch typing will become a specialized skill

AI dictation is getting so good that I think touch typing will become a specialized skill again in 5-10 years

–Jasmine Sun

More articles, videos, talks

Cross-compiling C and Go via cgo with Bazel

Cross-compiling pure Go is dead easy. Go with C—not so much. Uros Popovic demonstrates how Bazel, a build system developed by Google, can help.

Fan-In: Can we Go faster? | Just a Random Dev Blog

Collecting results from mutliple goroutines requires an efficient fan-in implementation. How does the canonical fan-in code (where one goroutine per incoming channel collects the results into a single output channel) compare against alternatives based on reflection, batching, or a single-goroutine loop?

The Perils of Pointers in the Land of the Zero-Sized Type – Fillmore Labs Blog

Go's optimization of zero-sized types can have a detrimental effect on pointer comparisons.

You Are Misusing Interfaces in Go - Architecture Smells: Wrong Abstractions | by Emre Savcı | GoTurkiye | Jun, 2025 | Medium

Interfaces in Go work different than in other languages. Emre Savcı explains how to use them right.

Why I'm excited about Go for agents - Hatchet Documentation

Want to write a multi-agent system within a single process? Then Go's concurrency is a perfect match.

BF16 in the Go Programming Language

Floating-point types are a compromise between dynamic range and precision. The BF16 type sacrifices even more precision for a larger exponent range. Go doesn't have a BF16 type. If the CPU supports BF16 (such as Alibaba Cloud's Yitian 710) and you want to use it in Go, you can use a mix of Ggo and assembly as outlined in this article.

You could automate (some) boilerplate Go error handling with a formatter

What if go fmt or gopls could add error handling automatically?

Projects

Libraries

GitHub - enrichman/httpgrace: Go net/http wrapper with graceful shutdown baked in.

Tired of writing HTTP server shutdown boilerplate all the time? httpgrace is a drop-in replacement for http with graceful shutdown code included.

GitHub - saiko-tech/sqleak: Go SQL Driver/Middleware to Detect Resource Leaks

When your app suddenly runs out of db connections and the linters don't deliver useful information, sqleak might be the last resort.

The UI Kit for templ | templUI

templUI is a UI component kit for a-h/templ, the templating alternative to Go's standard templates, which became popular along with stack like GoTH (Go, Templ, HTMX).

GitHub - bartventer/httpcache: Standards-compliant HTTP caching RoundTripper for Go clients (RFC 9111).

To clarify, this package implements a private, that is, client-side HTTP response cache. It's not a proxy cache.

Tools and applications

GitHub - ksamirdev/schedy: An open source, self-hostable, ultra-lightweight HTTP task scheduler for the weird and wonderful automation you want.

Send scheduled HTTP POST requests.

golizer.com

Generate your Go next project skeleton in a Web UI.

GitHub - crumbyte/noxdir: Terminal utility for visualizing file system usage.

Too many disks in your system? NoxDir can help!

GitHub - joelseq/go-svelte-spa: Golang backend with a Svelte SPA frontend

An example setup of a Go backend with a SvelteKit single-page app (SPA) frontend.

GitHub - itsManjeet/rlxos: A truly modern implementation of GNU/Linux distribution

What if there was a Linux distro whose userland consists entirely of Go binaries? Wait - such a distro exists ... even though it's just a proof of concept.

GitHub - doganarif/portfinder: A fast, lightweight Go-based CLI tool to detect and manage processes using network ports—featuring project awareness, Docker support, and an interactive terminal UI.

What's sitting on port 8080? pf checks port usage for you and can even kill programs that occupy a port. No more "Port already in use" messages.

Completely unrelated to Go

The Ultimate Personal Security Checklist | Digital Defense

As cyberthreats increase, so must your defense measures. Here is a checklist.

Happy coding! ʕ◔ϖ◔ʔ

Questions or feedback? Drop me a line. I'd love to hear from you.

Best from Munich, Christoph

Not a subscriber yet?

If you read this newsletter issue online, or if someone forwarded the newsletter to you, subscribe for regular updates to get every new issue earlier than the online version, and more reliable than an occasional forwarding. 

Find the subscription form at the end of this page.

How I can help

If you're looking for more useful content around Go, here are some ways I can help you become a better Gopher (or a Gopher at all):

On AppliedGo.net, I blog about Go projects, algorithms and data structures in Go, and other fun stuff.

Or visit the AppliedGo.com blog and learn about language specifics, Go updates, and programming-related stuff. 

My AppliedGo YouTube channel hosts quick tip and crash course videos that help you get more productive and creative with Go.

Enroll in my Go course for developers that stands out for its intense use of animated graphics for explaining abstract concepts in an intuitive way. Numerous short and concise lectures allow you to schedule your learning flow as you like.

Check it out.


Christoph Berger IT Products and Services
Dachauer Straße 29
Bergkirchen
Germany

Don't miss what's next. Subscribe to The Applied Go Weekly Newsletter:
LinkedIn
Powered by Buttondown, the easiest way to start and grow your newsletter.