Go 1.25 Draft Release Notes: A First Look

This week, the Go team has published the draft release notes for Go 1.25. You can find the draft notes here.

Be aware that the content of the draft notes may change before the final release.

In this post, I will go through some of the highlights of the draft release notes and provide my thoughts on them.

New Method: WaitGroup.Go

sync package's WaitGroup now introduces a new method Go.

The draft notes states that this method makes the common pattern of creating and counting goroutines more convenient.

Example

Say we have a main function that creates a couple of goroutines and waits for them to finish using sync.WaitGroup.

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup

    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println("Start some work...")
        time.Sleep(2 * time.Second)
    }()

    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println("Start another work...")
        time.Sleep(2 * time.Second)
    }()

    wg.Wait()
    fmt.Println("All work done!")
}

// Output:
// Start some work...
// Start another work...
// All work done!

In this example, we create a sync.WaitGroup and add 1 to it for each goroutine we create. Then we call wg.Wait() to block the execution until all the goroutines get done.

The new Go method allows us to simplify the code by removing the need to call wg.Add(1) for each goroutine like below:

func main() {
    var wg sync.WaitGroup

    wg.Go(func() {
        defer wg.Done()
        fmt.Println("Start some work...")
        time.Sleep(2 * time.Second)
    })

    wg.Go(func() {
        defer wg.Done()
        fmt.Println("Start another work...")
        time.Sleep(2 * time.Second)
    })

    wg.Wait()
    fmt.Println("All work done!")
}

To me, this solution looked elegant and less error-prone way to create goroutines and wait for them to finish. But at the same time, it adds complexity to the code, allowing the developers to choose between two different ways to create goroutines, which Go has tried to avoid in the past.

In reality, I seldom use sync.WaitGroup at job, so I don't have a strong opinion on this change honestly.

Tool: Vet

The go vet tool has been updated to include new checks.

Misplaced sync.WaitGroup.Add

Just like I showed earlier, normally sync.WaitGroup.Add is called before starting a goroutine and the execution of sync.WaitGroup.Done. go vet currently does not check where sync.WaitGroup.Add is called, but it will now check if sync.WaitGroup.Add is called at the right place.

Building Address

go vet checks if net.JoinHostPort is used to construct an address.

Besides net.JoinHostPort, you can write fmt.Sprintf("%s:%d", host, port) to construct an address for net.Dial. However, building an address using fmt.Sprintf is not applicable when the address is IPv6.

Thus, go vet will suggest using net.JoinHostPort instead of fmt.Sprintf to construct an address.

Runtime: panic formatting

Say we have a code snippet like this:

package main

import "fmt"

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Caught panic:", r)
            panic(r)
        }
    }()

    panic("panic occurred!")
}

This code first panics with the message panic occurred! and then recovers from it. Then it panics again in the deferred function with the original panic message.

The output of this code is as follows:

Caught panic: panic occurred!
panic: panic occurred! [recovered]
    panic: panic occurred!

goroutine 1 [running]:
main.main.func1()
    /tmp/sandbox1238924523/prog.go:9 +0x7e
panic({0x49b5a0?, 0x4d9578?})
    /usr/local/go-faketime/src/runtime/panic.go:792 +0x132
main.main()
    /tmp/sandbox1238924523/prog.go:13 +0x3e

Program exited.

Let's take a closer look at the first part of the output:

Caught panic: panic occurred!
panic: panic occurred! [recovered]
    panic: panic occurred!

The program first prints Caught panic: panic occurred! and then outputs two identical panic messages with indentation.

In Go1.25, this output will be changed to:

panic: panic occurred! [recovered, repanicked]

The release draft notes as below:

On Linux systems with kernel support for anonymous VMA names (CONFIG_ANON_VMA_NAME), the Go runtime will annotate anonymous memory mappings with context about their purpose. e.g., [anon: Go: heap] for heap memory. This can be disabled with the GODEBUG setting decoratemappings=0.

This statement basically means that the Go runtime add notes about how the memory is used.

Since I assume not many Gophers handle panic in production code, this change will not affect most of us. But I think this is a good change because it makes the panic message more informative and easier to understand.

Compiler

New compiler format

The draft notes says that The compiler and linker in Go 1.25 now generate debug information using DWARF version 5.

DWARF (Debugging With Attributed Record Formats) is a widely used debugging data format, and the DWARF5 is the latest version of it released in 2017.

At the time of writing(May 2025), dwarf package provides a Go implementation of the DWARF version 2 format, which was released in 1990's.

Nil pointer check

The compiler will check if the nil pointer check is done properly.

Let's say we have a code snippet like this:

package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("some_file.txt")
    fileName := f.Name()
    if err != nil {
        return fmt.Println("error:", err)
    }
    fmt.Println("fileName:", fileName)
}

In most cases, we make sure that the returned error is nil before going on to refer to the returned value, f in this case. However, this code snippet does not check if error is nil before referring to f.

This code runs fine with Go 1.24, but in Go 1.25, the compiler will find it unacceptable and panic with a nil pointer exception.

I feel this change will affect a certain number of existing codebases. I think that the most Go developers know that checking error first is a good practice, and when they don't, they have a good reason for it. So forcing the nil check by the compiler does not sound like a best approach to me.

Conclusion

I have gone through some of the highlights of the draft release notes for Go 1.25. I liked some of the changes, while I am not very comfortable with the other changes.

But version update is something always that make me feel excited. I will be looking forward to the final release of Go 1.25 and I hope you are too.

1
Subscribe to my newsletter

Read articles from Douglas Shuhei Takeuchi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Douglas Shuhei Takeuchi
Douglas Shuhei Takeuchi

I am Douglas Takeuchi, a Tokyo-based software engineer with a background in fintech and a particular expertise in developing prepaid card systems using Golang. I also have substantial experience in Python and Javascript, making me a versatile and skilled programmer. Born in Tokyo, Japan, in 1998, I now reside in the bustling Tokyo metropolitan area. I hold a bachelor's degree in social science, which has provided me with a well-rounded perspective in my career. Beyond my professional work, I'm a dedicated musician and language enthusiast. I've been an active musician since my university days, and my music has resonated with audiences worldwide, amassing thousands of plays. One of my notable projects is the creation of a web application designed for university students to seek advice and guidance on various aspects of university life and their future career paths, similar to Yahoo Answers. In the world of coding, I feel most comfortable in Golang. I place a strong emphasis on achieving goals as a team, often coming up with collaborative solutions for success. This might involve organizing workshops to delve deeper into new technologies and collectively improve our skills. As a software engineer, I bring creativity, problem-solving skills, and a determination to excel. My commitment to my craft and the "Fake it till you make it" mentality drive my continuous growth and success. "Fake it till you make it" is my guiding principle, encouraging me to step out of my comfort zone, take on challenges, and learn from every experience, ultimately propelling me toward success.