Creating errors

February 3, 2025

Just over a year ago I started this (mostly) daily mailing list, with the goal of going through the entire Go spec, to make it easier for you (and me!) to understand.

That mission ended on Friday.

So starting today, this list will take on a less structured format. I’ll still be talking about Go. And whenever the spec is updated (as is due to happen any day now, once Go 1.24 is officially released), I’ll revisit the spec to explain what’s new.

But for now, I want to answer a couple reader questsions. I asked for these a few weeks ago, and they’re still welcome. If something bothers you about Go, or just doesn’t make sense, hit that « REPLY » button, and let me know. I’ll try to address it here. And maye also in an upcoming video (I hope to get back into making videos next month, as I should be back into a normal office/studio by then).

For today, the question I want to answer is:

I would like to ask about fmt.Errorf. How is that function able to coerce a string into an error? Is it possible for an end user to type cast a string into an error directly?

To answer this question, the first thing we need is a refresher on what an error in Go even is! Let’s refresh our memory by looking at the relevant section of the spec:

Errors

The predeclared type error is defined as

type error interface {
	Error() string
}

So that’s really it. An error, in Go, is any value which satifies that interface.

So is it possible for an end user to convert a string into an error directly? Yes. It’s quite trivial. All you need to do is create a type that implements that interface:

type MyError string

func (e MyError) Error() string {
  return string(e)
}

That’s it!

Now MyError satisfies the error interface.

So how does fmt.Errorf return an error? It does the same exact trick. We can look at the source code to see exactly what it does:

func Errorf(format string, a ...any) error {
	/* .. snip .. */
		w := &wrapError{msg: s}
		w.err, _ = a[p.wrappedErrs[0]].(error)
		err = w
	/* .. snip .. */
	return err
}

There’s much more in that function, but we’re only looking at part of it here, for explanatory purposes. In essence, it reutrns avalue of type *wrapError. So let’s look at that type:

type wrapError struct {
	msg string
	err error
}

func (e *wrapError) Error() string {
	return e.msg
}

And there you have it. fmt.Errorf returns a *wrapError value which implements the error interface by virtue of having an Error() string method on it.


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Cancelation causes

We’ve seen that we can cancel contexts in a few ways–explicitly, or via timeouts. And that canceling a parent context also cancels all of its children. But in all of these cases we get only two possible error values: context.Canceled or context.DeadlineExceeded. What if you wish to express additional details about a cancelation? This is where the concept of a ‘cause’ comes into play… Overview … The WithCancelCause, WithDeadlineCause, and WithTimeoutCause functions return a CancelCauseFunc, which takes an error and records it as the cancellation cause.


Error interfaces

On Monday I talked about error structs. Let’s continue that theme, with error interfaces. To recap the problem: You want to the caller of your function to be able to extract context from an error, without parsing error strings. Or maybe you even want to include context that isn’t part of the error string at all! Monday’s solution to this problem had us using custom structs. A similar, but often more flexible approach, is to use an error interface.


The Context API contract

Today we come to the core of the context package: The Context interface itself. type Context type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any } A Context carries a deadline, a cancellation signal, and other values across API boundaries. Context’s methods may be called by multiple goroutines simultaneously. For clarity, I’ve removed all of the documentation for each of the interface methods in the above quote—we’ll get to those in following emails, and include those there.

Get daily content like this in your inbox!

Subscribe