Join me later today at 3:00pm EDT (19:00 UTC), for more live coding. Subscribe on YouTube and hit the Notify me button so you don’t miss it! Yesterday we looked at how to handle errors from multiple goroutines using an error channel. While functional, the approach is somewhat complicated, and easy to get wrong. Enter errgroup. While not part of the standard library, it is maintained by the Go team, and is widely used in the Go ecosystem.| Boldly Go
Join me tomorrow, August 29, at 3:00pm EDT (19:00 UTC), for more live coding. Subscribe on YouTube and hit the Notify me button so you don’t miss it! Last week we looked at how to handle errors from multiple goroutines using a mutex and a shared error variable. While this works for many common scenarios, it has some limitations. For example, it only captures the first error encountered and ignores any subsequent errors.| Boldly Go
Join me today, August 22, at 3:00pm EDT (19:00 UTC), more live coding. Subscribe on YouTube and hit the Notify me button so you don’t miss it! We’ve been looking at different ways of managing multiple goroutines. But what if one (or more) the functions called in a goroutine might return an error? So far, we don’t have a solution to that in our toolbox. There are a few different ways we can accomplish this.| Boldly Go
Join me again, this coming Friday, August 22, at 3:00pm EDT (19:00 UTC), more live coding. Subscribe on YouTube and hit the Notify me button so you don’t miss it! Last week we looked at a common mistake with sync.WaitGroup, and how Go 1.25’s new go vet check can help catch it. To recap, the mistake is calling wg.Add(1) from within a goroutine, which can cause wg.Wait() to return prematurely:| Boldly Go
Can you spot the mistake in this code? for i := range 100 { go func() { wg.Add(1) defer wg.Done() fmt.Println(i) }() } wg.Wait() At first glance, this code probably looks fine. We’re calling wg.Add(1) at the beginning of each task. We’re deferring the call to wg.Done(), so it’s called when the function exits. Each Add call has a matching Done call. We’re properly waiting for all tasks to complete with wg.| Boldly Go
Join me tomorrow, Friday, August 15, at 3:00pm EDT (19:00 UTC), for a live stream where I’ll be building a new feature in my open-source project. Subscribe on YouTube and hit the Notify me button so you don’t miss it! So I’m taking a short break from talking about goroutines (mostly) to mention the relese of Go 1.25! Go 1.25 is a rather insignificant release, in the grand sceme of things.| Boldly Go
Join me this Friday, August 15, at 3:00pm EDT (19:00 UTC), for a live stream where I’ll be building a new feature in my open-source project. Subscribe on YouTube and hit the Notify me button so you don’t miss it! One thing we can do with sync.WaitGroup, that may not be immediately obvious, is to call Add from within a goroutine that’s managed by the wait group itself: wg := &sync.| Boldly Go
Join me next Friday, at 3:00pm EDT (19:00 UTC), for a live stream where I’ll be building a new feature in my open-source project. Subscribe on YouTube and hit the Notify me button so you don’t miss it! Yesterday I introduced the sync.WaitGroup type. Today I want to look at some possible ways to improve the same code. First, the code as we left off yesterday: wg := &sync.WaitGroup{} for i := range 10 { wg.| Boldly Go
It’s been a year since my last live stream, as I was moving across the globe. I’m finally settled (enough) to start up again. Join me, as I’ll begin building a new feature in my open-source project. The next session will be August 15, at 19:00 UTC. Subscribe on YouTube and hit the Notify me button so you don’t miss it! Yesterday we wrote some Go code to wait for goroutines using a channel:| Boldly Go
Yesterday we looked at some sample code with goroutines: for i := range 10 { go func() { fmt.Println(i) }() } But if you ran it, you probably saw no output. This is because the main goroutine exits before the 10 new goroutines have a chance to run. How do we solve this? We need some sort of synchronization mechanism in place, to ensure we wait for all goroutines to finish before exiting the program.| Boldly Go
Today I’m starting a new series of posts on the topic of goroutines. In the past, I’ve mostly walked through existing documentation (the Go spec, docs for standard library packages, etc), and provided my commentary and explanation. Starting today, instead, I’ll be going on a topical journey through goroutines. I will, of course, touch on the spec, and some standard library packages, but I’ll be taking my own path through the topic.| Boldly Go
Today I want to share a trick I stumbled upon. It won’t matter to most of you. If it does matter to you, it will probably save you a bunch of headaches! First, some background. Have you ever had to use an outdated dependency for $REASONS? If not, you can skip today’s email, unless you’re just curious. One of my clients is still using MongoDB 3.2. MongoDB was EOLed more than a year before anyone had ever heard the term “COVID”.| Boldly Go
I’ve been on vacation for the last week, so haven’t written much. Let’s finally finish up the context package series with some reader feedback and questions! Joost Helberg wrote in with an interesting observation about context.TODO. In his own words: With regards to context.TODO, back in my early Go days, I thought the TODO was about the intent of this context. Like context.Background is for a background task and context.WithCancel for a job that can be canceled.| Boldly Go
This is it! The last of the context package functions that we will cover in this series. Well, almost. I actually have a couple reader feedback and question emails to respond to, which I’ll do next, before startinga a new series. Speak of a new series: I’m still looking for suggestions! I have a couple ideas, but I’d rather do something you’re itching to learn about, than whatever random idea I come up with.| Boldly Go
func WithValue func WithValue(parent Context, key, val any) Context WithValue returns a derived context that points to the parent Context. In the derived context, the value associated with key is val. Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context.| Boldly Go
We’re almost done going through the context package. What would you like me to cover next? If you have a suggestion, hit reply, and let me know! – func TODO func TODO() Context TODO returns a non-nil, empty Context. Code should use context.TODO when it’s unclear which Context to use or it is not yet available (because the surrounding function has not yet been extended to accept a Context parameter).| Boldly Go
After this detour through context key types, let’s get back to the GoDoc for our context package. Next up: context.Background(): func Background func Background() Context Background returns a non-nil, empty Context. It is never canceled, has no values, and has no deadline. It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests. Why would you ever want a context that essentially does nothing?| Boldly Go
So we’ve finally settled on a suitable type for our context key types: string, or maybe a non-empty struct. We also understand that to guard against key collisions, we need our custom type to be un-exported. type contextKey string const KeyUserID = contextKey("user_id") Now code anywhere in your project can use the context key without fear of collision. Done! Right? I’m sure you know the fact that I’m asking means there’s more we can do.| Boldly Go
First, a correction! An astute reader pointed out that I made a small mistake in my post on June, 10, with regard to string context keys. My code example showed: type contextKey string const ( KeyUserID = "user_id" KeyTransactionID = "transaction_id" KeyMessageID = "message_id" ) But it should have been: type contextKey string const ( KeyUserID contextKey = "user_id" KeyTransactionID contextKey = "transaction_id" KeyMessageID contextKey = "message_id" ) This is a nasty kind of bug, because th...| Boldly Go
First off, an apology for being rather unreliable with my “daily” emails. I’m in the middle of some traveling (was in the UK and Netherlands last week, and will be on a US road trip starting tomorrow), so I’m writing when I have a spare moment, which isn’t that often. I’ve been talking about finding the ideal type for a context key. So far I’ve looked at empty structs (e.g. struct{}), integers, and strings.| Boldly Go
By now we’ve looked at empty structs (struct{}), and integers as possible context key types. Today, let’s consider string context keys. type contextKey string const ( KeyUserID = "user_id" KeyTransactionID = "transaction_id" KeyMessageID = "message_id" ) How does this stack up against our 5 criteria? Must be comparable. ✅ Check. Use minimal memory. ✅ Using a string will typically use a bit more memory than an integer (typically 32 bytes vs 16), but still quite minimal.| Boldly Go
We’re looking at different types for context keys. So far, we’ve looked at empty structs (struct{}), and found it to be less than ideal. Today, let’s consider integer context keys. This seems handy, right? type contextKey int const ( KeyUserID int = iota KeyTransactionID KeyMessageID . . . KeyFoo ) Let’s see how it stacks up to our 5 criteria: Must be comparable. ✅ No problem! Use minimal membory. ✅ int doesn’t have as small a memory footprint as struct{}’s zero bytes, but it...| Boldly Go
So we’ve established that context keys should be of an unexported type in your package. That still leaves a lot of options. Which type is ideal? Today we’ll look at some options, and their pros and cons. Before looking at specific types, let’s consider what sorts of things we want from such a type. It must be comparable, or it won’t work as a key. This means we can’t use function or channel types, for example.| Boldly Go
Last week we saw the potential dangers of context key collisions. Today let’s look at how to avoid this problem. The GoDoc we looked at last week gave us a clue: // packages should define keys as an unexported type to avoid // collisions. Let’s dive into what this is suggesting, and why it works. First: context.WithValue accepts any comparable type as a key. This includes custom types. And by using a custom type that’s unexported, we can ensure that no other package uses our keys.| Boldly Go
Let’s talk about keys for context values. type Context type Context interface { … // A key identifies a specific value in a Context. Functions that wish // to store values in Context typically allocate a key in a global // variable then use that key as the argument to context.WithValue and // Context.Value. A key can be any type that supports equality; // packages should define keys as an unexported type to avoid // collisions.| Boldly Go
I’ve already talked a bit about context values, and when not to use them. For a recap, take a look at these previous posts, so I don’t have to rehash those points today: Context abuse Context values and type safety When not to use context values So from here on out, we’re assuming that you have a legitimate reason to store values in a context. How can/should you go about it?| Boldly Go
type Context type Context interface { … // If Done is not yet closed, Err returns nil. // If Done is closed, Err returns a non-nil error explaining why: // DeadlineExceeded if the context's deadline passed, // or Canceled if the context was canceled for some other reason. // After Err returns a non-nil error, successive calls to Err return the same error. Err() error Done() (covered yesterday) and Err() (today) are the two methods you’ll virtually alway use when writing code to honor cont...| Boldly Go
May I rant for a moment? I don’t like the way GoDoc works for structs and interfaces. And below is a demonstration as to why. It’s not possible to link directly to an interface method or struct field, and the formatting is ugly. sigh type Context type Context interface { … // Done returns a channel that's closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled.| Boldly Go
On Friday we looked at the context.Context interface as a whole. Now let’s go back and look at each method in greater detail. type Context type Context interface { // Deadline returns the time when work done on behalf of this context // should be canceled. Deadline returns ok==false when no deadline is // set. Successive calls to Deadline return the same results. Deadline() (deadline time.Time, ok bool) So there are two general cases here:| Boldly Go
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 ...| Boldly Go
func WithTimeout func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete: func slowOperationWithTimeout(ctx context.Context) (Result, error) { ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() // releases resources if slowOperation c...| Boldly Go
Today we’re talking about canceling contexts based on time. Arguably, this should have come first, as it’s a little simpler to work with than the explicit cancelation we talked about a week ago. But once again, I’m going in essentially alphabetical order, so it’s what we have… func WithDeadline func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) WithDeadline returns a derived context that points to the parent context but has the deadline adjusted to be no later than d.| Boldly Go
About a week ago, I talked about the two error types that a context may return: context.Canceled and context.DeadlineExceeded. But what if you want to convey some other error? Maybe you want to distinguish between a context that was canceled because the server is shutting down due to a SIGINT, versus becasue the request was canceled by the client, for example? Enter Cause. func Cause func Cause(c Context) error Cause returns a non-nil error explaining why c was canceled.| Boldly Go
I’m jumping out of order a bit today, to cover a more basic function first, before the more advanced version. Bear with me. func WithCancel func WithCancel(parent Context) (ctx Context, cancel CancelFunc) WithCancel returns a derived context that points to the parent context but has a new Done channel. The returned context’s Done channel is closed when the returned cancel function is called or when the parent context’s Done channel is closed, whichever happens first.| Boldly Go
One of the drawbacks of this format I’ve chosen—reading through a document, and providing commentaty, in order—is that sometimes advanced topics come up before the foundational ones they rely on. Today is one such case. Typically, context cancelation is used to terminate an action in progress. But it can also be used to begin an action. One way to accomplish this is to wait for a cancelation signal in a goroutine:| Boldly Go
The context package exports two variables: Variables var Canceled = errors.New("context canceled") Canceled is the error returned by [Context.Err] when the context is canceled for some reason other than its deadline passing. var DeadlineExceeded error = deadlineExceededError{} DeadlineExceeded is the error returned by [Context.Err] when the context is canceled due to its deadline passing. That’s it. These are the only two types of errors that a context’s Err() method is allowed to return.| Boldly Go
Overview … The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines. Contexts are built using layers. Every time you call one of the context.With* functions, the orginal context is wrapped in a new layer. This is the “magic” that makes contexts concurrency safe by design–they’re never mutated once they’re created. ctx := context.Background() // Create an original context ctx, cancel = context.| Boldly Go
Storing values in a context is quite flexible, but comes without strict type safety, and obscures the API. So what can we do about this? First, for type safety, we’re limited to runtime assertions: userID := ctx.Value(userIDKey).(string) But this can panic if we ever get an unexpected value (or even nil). So to make it safer, we can use the two-variable assignment form, which yields a bool indicating success: userID, ok := ctx.| Boldly Go
Last week I asked whether or not it’s a good idea to pass things like a user-scoped database handle or logger via a context value. Before I provide my direct answer, I want to take a short detour… Go is a (mostly) strictly-typed language. This gives us certain guarantees. When we have a variable of type int, we know it doesn’t contain the value "cow", because that’s not an integer.| Boldly Go
We’re nearly through the overview of the context package, when we come across this seemlingly straightforward sentence: Overview … Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. But is that really so straight forward? Let’s consider some examples. Let’s first tackle what probably is straight-forward: For garden variety optional parameters, context is the wrong tool! Do this: // Connect connects to the...| Boldly Go
After my last email, discussing context.TODO(), I got this question from Bruno Schaatsbergen: Thanks for the blog Jonathan, as always a pleasure to read. Regarding the last part on testing, any particular reason why you do not recommend using the new T.Context and B.Context methods? Great question! Go 1.24 added new methods to the testing package: T.Context, B.Context, and F.Context. These methods return a context which is canceled when the test, benchmark, or fuzz test, respectively, is canc...| Boldly Go
Overview … Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use. Two pieces of advice in today’s section. I’m going to tackle them in reverse, though. Use context.TODO() if you are unsure which Context to use. Functionally context.TODO() is identical to context.Background(). In fact, the only difference between the two, is their implementation of the String() method, as we can see by reading the source:| Boldly Go
Overview … The Context should be the first parameter, typically named ctx: func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... } This short sentence includes two “rules”. Let’s talk about both, why they exist, and when to (possibly) violate them. Context should be the first parameter. Why this is a rule is probably a lot less important than following the rule. But let’s consider the why anyway.| Boldly Go
On Friday, I shared a short code snippet, and asked how it could be improved. If you missed it, go read the challenge now then come back. I got a good number of responses, so today I’m going to share a few of them, and offer my own answer. First off, Jim Kalafut, and several others, the code I shared. One line was return nil, but should have been return decodedBytes, nil.| Boldly Go
Today I want to share a bit of code I found in the wild, and challenge you to improve it. First off, let me make it clear that this code “works”. It has unit tests, and the tests all pass correctly. So I’m not looking for bugs, per se. func decodeString(encoded string) ([]byte, error) { var decodedBytes []byte var err error trimmed := strings.TrimRight(encoded, "=") decodedBytes, err = base64.StdEncoding.DecodeString(trimmed) if err !| Boldly Go
Overview … Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation: Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. This is discussed further in https://go.dev/blog/context-and-structs. We’ve already discussed how the proliferation of the ctx context.Context argument can start to look more cluttered than my desk on a Friday...| Boldly Go
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 CancelC...| Boldly Go
As already mentioned, the context.Context type serves two purposes: It manages cancelation signals, and it allows passing request-scoped data. In my opinion, these two purposes are distinct enough that they probably should be handled by two separate mechanisms. But alas, that’s not what we have. However, I think it’s still useful to think of the two uses as distinct. Most often (at least in a well-designed application), the primary purpose of a context is the propegation of cancelation si...| Boldly Go
Monday we looked at a very high-level view of what problems the context package is meant to solve. Namely, to propagate cancelation signales and/or request-scoped values through the call stack of an application. Today we’ll take a brief look at how context “plumbing” works—and answer they why of all those mysterious context.Context function parameters you’ve probably seen, being passed around more than a virus in a daycare. Overview … Incoming requests to a server should create a ...| Boldly Go
Hey! My apologies for being absent for the last few weeks. I’ve been incredibly busy closing on a new house and moving. We’re still settling, but it’s time to start this up again. So today I’m starting a short series on context! Specifically, I’ll be going through the context package in the standard library, explaining what it does, and offering my advice, much like I went through the Go spec in the past.| Boldly Go
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.| Boldly Go
The standard library gives us only very basic error capabilities. For the most part, we can create strings-as-errors: return errors.New("oh no") And we can wrap errors with additional textual context: if err := doSomething(); err != nil { return fmt.Errorf("doSomething: %w", err) } But the fact that the built-in error type is an interface gives us a ton more flexibility than this to include arbitrary information with our errors. There are a few ways we can include additional information in er...| Boldly Go
Yesterday, Shay Nehmad and I, and close to 20 other people, recorded the 100th episode of the Cup o’ Go podcast. It’s been just over two years since the first episode came out, and in that time we’ve made some good friends, and built a bit of a community. If you haven’t listened yet, I invite you to listen to the replay of the live episode, which is also about celebrating the newly released Go 1.| Boldly Go
Last week I talked about how to create your own errors. Expanding on that idea, here’s a trick I like to use: Constant errors! Constants in Go are limited to simple underlying types: bool, string, and the numeric types. If we create our own type, which both satisfies the error interface, and has such an underlying type, we can then create constant error values: type errString string func (e errString) Error() string { return string(e) } const ErrTooManyFrogs = errString("too many frogs") Okay.| Boldly Go
Go 1.24 is released! Yay! That means a short re-visit to the Go spec, as we look at things that have changed. From the release notes we see that there’s really only one significant change: Changes to the language¶ Go 1.24 now fully supports generic type aliases: a type alias may be parameterized like a defined type. See the language spec for details. For now, the feature can be disabled by setting GOEXPERIMENT=noaliastypeparams; but the aliastypeparams setting will be removed for Go 1.| Boldly Go
Yesterday I answered a reader question that included some often confused terminology. It’s nothing to be ashamed of, and it’s quite a common mistake. But I thought this would be a good opportunity to clear it up. The question was: Is it possible for an end user to type cast a string into an error directly? Go doesn’t support type casting. Actually, that’s not entirely true. There’s no single definition of type casting.| Boldly Go
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.| Boldly Go
This is it! The final email in my 2+ year series on the Go spec. Today we cover the last ~paragraph of the spec, not accounting for the appendix, which is mostly just a history of major changes to the spec. Size and alignment guarantees For the numeric types, the following sizes are guaranteed: type size in bytes byte, uint8, int8 1 uint16, int16 2 uint32, int32, float32 4 uint64, int64, float64, complex64 8 complex128 16 Most notably, int is omitted from this list.| Boldly Go
Package unsafe … The function String returns a string value whose underlying bytes start at ptr and whose length is len. The same requirements apply to the ptr and len argument as in the function Slice. If len is zero, the result is the empty string "". Since Go strings are immutable, the bytes passed to String must not be modified afterwards. [Go 1.20] The function StringData returns a pointer to the underlying bytes of the str argument.| Boldly Go
Package unsafe … The function Slice returns a slice whose underlying array starts at ptr and whose length and capacity are len. Slice(ptr, len) is equivalent to (*[len]ArbitraryType)(unsafe.Pointer(ptr))[:] except that, as a special case, if ptr is nil and len is zero, Slice returns nil [Go 1.17]. The len argument must be of integer type or an untyped constant. A constant len argument must be non-negative and representable by a value of type int; if it is an untyped constant it is given typ...| Boldly Go
I mentioned a few days ago that pointer arithmetic is possible through the unsafe package. Here’s how to do it. Package unsafe … The function Add adds len to ptr and returns the updated pointer unsafe.Pointer(uintptr(ptr) + uintptr(len)) [Go 1.17]. The len argument must be of integer type or an untyped constant. A constant len argument must be representable by a value of type int; if it is an untyped constant it is given type int.| Boldly Go
You’ll likely remember that certain built-in functions can sometimes evaluate to constants, rather than variables. For example: var x [10]int var y []int len(x) // evaluates to a constant at compile time len(y) // evaluates to a variable at runtime Package unsafe … A (variable of) type T has variable size if T is a type parameter, or if it is an array or struct type containing elements or fields of variable size.| Boldly Go
Package unsafe … Computer architectures may require memory addresses to be aligned; that is, for addresses of a variable to be a multiple of a factor, the variable’s type’s alignment. The function Alignof takes an expression denoting a variable of any type and returns the alignment of the (type of the) variable in bytes. For a variable x: uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0 I don’t think I’m qualified to really talk much more about this.| Boldly Go
Package unsafe … The function Offsetof takes a (possibly parenthesized) selector s.f, denoting a field f of the struct denoted by s or *s, and returns the field offset in bytes relative to the struct’s address. If f is an embedded field, it must be reachable without pointer indirections through fields of the struct. For a struct s with field f: uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f)) I expect this is neither too confusing, nor too useful for mos...| Boldly Go
Package unsafe … The functions Alignof and Sizeof take an expression x of any type and return the alignment or size, respectively, of a hypothetical variable v as if v was declared via var v = x. Grammar nit! That should be “…as if v were…” But nobody cares about that. While Alignof is unlikely to be used outside of CGO, or low-level optimizations, Sizeof can be useful for us mere mortals.| Boldly Go
Today we’re venturing into dangerous territory… the unsafe package! You can go years working in Go (as I have) without ever using unsafe, or just barely bumping into it. On the other hand, somd developers spend significant time using unsafe, to get the utmost performance out of their code, by working around the type system. Using unsafe can be powerful and beneficial. But… do it with the understanding that you’re giving up the benefits of memory and type safety, and cross-platform com...| Boldly Go
Run-time panics Execution errors such as attempting to index an array out of bounds trigger a run-time panic equivalent to a call of the built-in function panic with a value of the implementation-defined interface type runtime.Error. That type satisfies the predeclared interface type error. The exact error values that represent distinct run-time error conditions are unspecified. package runtime type Error interface { error // and perhaps other methods } We’ve already covered the built-in pa...| Boldly Go
Today we’re looking at errors, and the built-in error interface. For a concept that’s so central to the Go ethos, the spec has surprisingly little to say about it: Errors The predeclared type error is defined as type error interface { Error() string } It is the conventional interface for representing an error condition, with the nil value representing no error. For instance, a function to read data from a file might be defined:| Boldly Go
Program execution A complete program is created by linking a single, unimported package called the main package with all the packages it imports, transitively. The main package must have package name main and declare a function main that takes no arguments and returns no value. func main() { … } Program execution begins by initializing the program and then invoking the function main in package main. When that function invocation returns, the program exits.| Boldly Go
I’ve been on an unannounced, unplanned, month-long hiatus, what with moving my family across the globe. But with the new year starting, and the worst of the chaos behind me, I’m going to try to get back in the saddle again, and pick up where I left off. And what better way to initialize 2025 than with talking about init? Program initialization … Package initialization—variable initialization and the invocation of init functions—happens in a single goroutine, sequentially, one packag...| Boldly Go
I wasn’t exactly planning to take last week off from the daily emails, but with Thanksgiving preparations, and still living out of a suitcase, it just happened that way. But I’m back now, and ready to talk about Program initialization Program initialization The packages of a complete program are initialized stepwise, one package at a time. If a package has imports, the imported packages are initialized before initializing the package itself.| Boldly Go
We’ve been looking at the order of package variable initialization. What if these rules are confusing? Or even non-desterministic for you? Or maybe you simply want to do something more advanced than is feasible in package variable declarations. Is there an alternative? There is! Package initialization … Variables may also be initialized using functions named init declared in the package block, with no arguments and no result parameters. func init() { … } Note that package variables can ...| Boldly Go
We’re nearing the end of the discussion on package initialization order. Monday should be the last day on that topic, but more on that shortly. Up to now, we’ve been looking at the deterministic order-of-initialization rules.Today’s topic is when that order is not defined. Package initialization … Dependency analysis is performed per package; only references referring to variables, functions, and (non-interface) methods declared in the current package are considered. If other, hidden,...| Boldly Go
Today’s section of the spec is a bit dense and technical. But don’t worry, it makes a lot of sense once we get to the end. Package initialization … Dependency analysis does not rely on the actual values of the variables, only on lexical references to them in the source, analyzed transitively. For instance, if a variable x’s initialization expression refers to a function whose body refers to variable y then x depends on y.| Boldly Go
Package initialization … The declaration order of variables declared in multiple files is determined by the order in which the files are presented to the compiler: Variables declared in the first file are declared before any of the variables declared in the second file, and so on. To ensure reproducible initialization behavior, build systems are encouraged to present multiple files belonging to the same package in lexical file name order to a compiler.| Boldly Go
Thank you to everyone who responded to yesterday’s pop quiz! I got a number of responses, both by email, and on LinkedIn and Mastodon. And the majority of responses made the exact same mistake I made, and assumed the code was invalid, because a is referenced before it’s initialized: var x = a var a = 3 And while it’s true that this is an error within a function, much to my surprise, it’s actually completely valid in package scope, as we’ll see now.| Boldly Go
It’s time for a pop quiz. Is the following code valid? var x = a var a = 3 Think about it. Hit reply with your answer, and explanation. I’ll provide a full answer and explanation tomorrow. Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)| Boldly Go
I’m sorry for missing a couple days. We took an long weekend with some extended family to visit the beach here in Guatemala. But I’m back, and ready to talk about … zero values! Program initialization and execution The zero value When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default v...| Boldly Go
Today we have something a bit different. Just a sample package. An example package Here is a complete Go package that implements a concurrent prime sieve. package main import "fmt" // Send the sequence 2, 3, 4, … to channel 'ch'. func generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'src' to channel 'dst', // removing those divisible by 'prime'.| Boldly Go
Yesterday I promised to teach you the one dirty trick they don’t want you to know about, to get around Go’s restriction on circular imports. It’s explained in this sentence, the last from the spec on the topic of imports. Import declarations … To import a package solely for its side-effects (initialization), use the blank identifier as explicit package name: import _ "lib/math" Did you catch it? It’s very subtle.| Boldly Go
First off, the spec provides an example of how to reference a symbol in an imported package, using different import alias options. I’ve already provided my own examples earlier, and it’s pretty straight forward, so we’ll just breeze through this part. Import declarations … Consider a compiled a package containing the package clause package math, which exports function Sin, and installed the compiled package in the file identified by "lib/math". This table illustrates how Sin is access...| Boldly Go
Today we’re looking at one of the more essoteric parts of the spec. Import declarations … Implementation restriction: A compiler may restrict ImportPaths to non-empty strings using only characters belonging to Unicode’s L, M, N, P, and S general categories (the Graphic characters without spaces) and may also exclude the characters !"#$%&’()*,:;<=>?[]^`{|} and the Unicode replacement character U+FFFD. Other than what’s stated in the quote, we’re not going to look at an exhaustive l...| Boldly Go
Newcomers to Go often try to use relative imports, and then are usually bitten by random weird problems. They seem to work sometimes, and not other times. What’s the deal? import "./foo" Import declarations … The interpretation of the ImportPath is implementation-dependent but it is typically a substring of the full file name of the compiled package and may be relative to a repository of installed packages. Okay. So this bit of the spec doesn’t help a whole lot.| Boldly Go
We’re continuing today our discussion of imports. Yesterday we left off with using explicit package names in an import clause when the package name differs from the last element of the import path. Is that the only reason to explicitly name your imports? No. Sometimes you need to disambiguate between two imports with the same package name. Or maybe you just like a shorter name. import ( log "github.com/sirupsen/logrus" // Shorter names are nice stdlog "log" // Oh, but we need to reference t...| Boldly Go
Today we have a very important topic… Import declarations An import declaration states that the source file containing the declaration depends on functionality of the imported package (§Program initialization and execution) and enables access to exported identifiers of that package. The import names an identifier (PackageName) to be used for access and an ImportPath that specifies the package to be imported. ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .| Boldly Go
I’m back! I took an extra week off, from what I planned, due to chaotic circumstances, but I’m back again now. I’m also in a new timezone—America/Central. So my daily emails may begin coming on a different erratic schedule than the previous erratic schedule you were accustomed to. Anyway, let’s get back into the spec! If you’ve done any Go coding at all, you’ve seen at least one package, the main package.| Boldly Go
A quick note to say that I’m taking next week off, for a move. I expect to return October 4 with more Boldy Go: Daily emails. Until then! We’re down to the final two built-in functions in Go. And these are probably functions you should not be using, except possibly in throw-away test code or during debugging. Bootstrapping Current implementations provide several built-in functions useful during bootstrapping. These functions are documented for completeness but are not guaranteed to stay i...| Boldly Go
Handling panics … The return value of recover is nil when the goroutine is not panicking or recover was not called directly by a deferred function. Conversely, if a goroutine is panicking and recover was called directly by a deferred function, the return value of recover is guaranteed not to be nil. To ensure this, calling panic with a nil interface value (or an untyped nil) causes a run-time panic.| Boldly Go
Yesterday we saw how panic plays with deferred functions, in terms of order of execution. Today we’ll take things up one level, and throw recover in there… there’s more to recover than just execution order. We’ll get to that next. Handling panics … The recover function allows a program to manage behavior of a panicking goroutine. Suppose a function G defers a function D that calls recover and a panic occurs in a function on the same goroutine in which G is executing.| Boldly Go
Handling panics … While executing a function F, an explicit call to panic or a run-time panic terminates the execution of F. Any functions deferred by F are then executed as usual. Next, any deferred functions run by F’s caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic.| Boldly Go
We’re ready for the section of the Go spec that talks about panic and recover, Go’s rough analogs to throw and catch. Handling panics Two built-in functions, panic and recover, assist in reporting and handling run-time panics and program-defined error conditions. func panic(interface{}) func recover() interface{} But before we dive into a discussion of how these things work, and how to use them, I want to offer some caution, in the form of one of the famous Go Proverbs:| Boldly Go
Today’s topic: Allocation! What? You thought allocation was relegated to languages like C and C++? Yeah, that’s actually pretty close to true. We’re not learning about malloc today. Rather, we’re learning about the built-in function new, which gives us just one (of many!) ways to allocate memory for a variable. Allocation The built-in function new takes a type T, allocates storage for a variable of that type at run time, and returns a value of type *T pointing to it.| Boldly Go
I always try to add my own commentary, examples, or other explanation in these emails. Today I’m not really sure what to add. min and max are such basic, and self-evident features, that the biggest surprise about these is that they’re such recent additions to the language. They were added just barely a year ago, with Go 1.21. I will add a bit after we read about min and max, though.| Boldly Go
Making slices, maps and channels The built-in function make takes a type T, optionally followed by a type-specific list of expressions. The core type of T must be a slice, map or channel. It returns a value of type T (not *T). The memory is initialized as described in the section on initial values. Call Core type Result make(T, n) slice slice of type T with length n and capacity n make(T, n, m) slice slice of type T with length n and capacity m make(T) map map of type T make(T, n) map map of ...| Boldly Go
A few of the built-in functions are very special, in that they can evaluate to constant expressions. len and cap are two such functions. But they aren’t always evaluated to constant expressions, sometimes they’re more normal-ish runtime functions. Length and capacity … The expression len(s) is constant if s is a string constant. The expressions len(s) and cap(s) are constants if the type of s is an array or pointer to an array and the expression s does not contain channel receives or (n...| Boldly Go
Length and capacity … The capacity of a slice is the number of elements for which there is space allocated in the underlying array. At any time the following relationship holds: 0 <= len(s) <= cap(s) Recall that a slice is backed by a fixed-length array, which may have more elements than the slice. When this is the case, the capacity of the slice is said to be the current length of the slice, plus any unused elements in the backing array that extend beyond the final element of the slice.| Boldly Go
Length and capacity The built-in functions len and cap take arguments of various types and return a result of type int. The implementation guarantees that the result always fits into an int. Recall that int is either a 32- or 64-bit integer. So this means that the theoretical maximum length or capacity of the various types that support len and cap depends on the CPU architecture. However, this should not matter in practice, since you’d quickly exceed the available memory, before you had a s...| Boldly Go
Deletion of map elements The built-in function delete removes the element with key k from a map m. The value k must be assignable to the key type of m. delete(m, k) // remove element m[k] from map m We already looked at clear, which deletes everything in a map. delete lets you surgically remove individual keys from a map. Unlike some languages, which give you the means to return an element as it’s deleted, if you want to do that in Go, you’ll need to do it in two steps:| Boldly Go
Today we’re looking at what are almost certainly the least used built-in functions in Go. This is why I’m covering it all in a single day, even though there’s enough material here to potentially justify two or three. Manipulating complex numbers Three functions assemble and disassemble complex numbers. The built-in function complex constructs a complex value from a floating-point real and imaginary part, while real and imag extract the real and imaginary parts of a complex value.| Boldly Go