This site is being rebuilt and some pages are out of date. For current details, write to [email protected]. This notice goes away when the rebuild is done.

No analytics unless you allow it, no tracking. This site keeps in your browser the language you pick, the theme, its colour, which site you chose, the currency on the pricing page and that you closed this notice; signing in adds session cookies. The legal page has the details.

Sign in

Quiet Pager · Radar

What changed in Go, Rust and Solidity this week.

What changed this week, from each project's own release notes, proposals and issues, with an exercise you can run for each change.

Written by a model · reviewed by a person · published Mondays · Atom feed

Radar #001 · Week 39 · Sep 21 – 27, 2026from 4 items

On 24 September the Go team accepted three proposals: a tighter rule for converting integers to strings from language version 1.28, goroutine labels for every test, and a single signer interface for x/crypto/ssh. The Go blog introduced the experimental portable simd package in Go 1.27. There were no Go releases this week.

Worth knowinglanguage

spec: remove string(int)

What changed
Accepted: from language version 1.28, an integer can be converted to a string only if its type is byte or rune, or if it is an untyped rune constant such as 'A'. This lifts a check that go vet has made for years into the language itself.
Production impact
Code that converts other integer types with string(i) stops compiling once its module's go.mod says go 1.28 or later; go vet already reports these conversions today.
Try it
Run go vet ./... on your module: it reports each string(i) the new rule will reject.
Source
github.com/golang/go/issues/3939
Explain it and run it

Understand it, then run it

Converting an integer to a string with string(i) does not give you its digits. It gives you the character whose Unicode code point is i: string(65) is "A", not "65". This surprises almost everyone who meets it, which is why go vet has warned about it for years.

The accepted proposal makes the rule part of the language. From language version 1.28, string(i) compiles only when i is a byte or a rune, the two integer types that already mean "a character". If you want the digits, use strconv.Itoa(i).

Run it now

Todaygo
package main

import (
	"fmt"
	"strconv"
)

// Compiles on Go 1.27.1. string(i) with an int is the form the new rule
// removes from language version 1.28: it gives the character whose Unicode
// code point is i, never the digits of i.
func main() {
	for _, i := range []int{65, 0x1F600, -1} {
		fmt.Printf("i = %d\n", i)
		fmt.Printf("  string(i)        = %q\n", string(i))       // the form Go 1.28 removes
		fmt.Printf("  string(rune(i))  = %q\n", string(rune(i))) // the same character, said explicitly
		fmt.Printf("  strconv.Itoa(i)  = %q\n", strconv.Itoa(i)) // the digits, if that was meant
	}
}

What it printed when we ran it on Go 1.27.1

i = 65
  string(i)        = "A"
  string(rune(i))  = "A"
  strconv.Itoa(i)  = "65"
i = 128512
  string(i)        = "😀"
  string(rune(i))  = "😀"
  strconv.Itoa(i)  = "128512"
i = -1
  string(i)        = "�"
  string(rune(i))  = "�"
  strconv.Itoa(i)  = "-1"

Run sends this program (for Solidity, the contract and its tests) to our own sandbox, where it is compiled and run once, with no network, and what it printed or the test report comes back here. Nothing is kept. Runs are counted per visitor for the day so everyone gets a turn; the details are on the legal page.

Nice to knowtooling

testing: add goroutine labels to tests

What changed
Accepted: the testing package will label the goroutine running every test, benchmark, example and fuzz test with test.name (the function's name, such as TestParse) and test.iter (which run of it this is, counting from 0 up to -test.count).
Production impact
The source does not say.
Try it
Until it ships, set labels yourself with pprof.Do from runtime/pprof; a CPU profile then shows which labels each sample ran under.
Source
github.com/golang/go/issues/75047
Explain it and run it

Understand it, then run it

A goroutine can carry labels: small key and value pairs, such as test.name=TestParse. They cost nothing to read your code with, but profiles record them, so when you look at where a program spends its time you can see which piece of work each part belongs to.

The accepted proposal has the testing package set two labels on its own whenever it runs a test, benchmark, example or fuzz test: the test's name and which run of it this is. You will not need to change a single test to get them.

Run it now

Todaygo
package main

import (
	"context"
	"fmt"
	"runtime/pprof"
)

// Goroutine labels already exist: pprof.Do sets them, and CPU profiles record
// every sample with the labels of the goroutine that took it. The accepted
// proposal has the testing package set two of them for you, test.name and
// test.iter. This sets the same two by hand and reads them back.
func main() {
	labels := pprof.Labels("test.name", "TestParse", "test.iter", "0")
	pprof.Do(context.Background(), labels, func(ctx context.Context) {
		// Everything run here, and every goroutine started from here,
		// carries the labels in a profile.
		pprof.ForLabels(ctx, func(key, value string) bool {
			fmt.Printf("%s=%s\n", key, value)
			return true
		})
	})
}

What it printed when we ran it on Go 1.27.1

test.iter=0
test.name=TestParse

Run sends this program (for Solidity, the contract and its tests) to our own sandbox, where it is compiled and run once, with no network, and what it printed or the test report comes back here. Nothing is kept. Runs are counted per visitor for the day so everyone gets a turn; the details are on the legal page.

Nice to knowecosystem

x/crypto/ssh: one signer interface, SignerV2

What changed
Accepted: a SignerV2 interface that folds today's Signer, AlgorithmSigner and MultiAlgorithmSigner into one, with SignContext taking a context and the signature algorithm, and Algorithms listing the algorithms a key offers. New constructors come with it: NewSignerV2, NewSignerV2WithAlgorithms and NewCertificateSignerV2.
Production impact
SignerV2 drops DSA keys and the legacy PEM encryption of RFC 1423, and the proposal describes it as the API meant to replace the current one when x/crypto/ssh moves into the standard library.
Try it
Check whether your code still uses DSA keys or RFC 1423 encrypted PEM keys; neither will be accepted by SignerV2.
Source
github.com/golang/go/issues/74424
Explain it

Understand it, then run it

When an SSH client or server proves who it is, it signs data with its private key. In Go's x/crypto/ssh package, the thing that signs is a Signer. Over the years two more interfaces were added beside it, so that RSA keys could choose between signature algorithms, and code had to check which one a key implemented.

The accepted proposal replaces the three with one interface, SignerV2. You ask it which algorithms it supports, and you pass the algorithm you want when you sign.

After the change ships

go · the proposal's code; it does not compile until the change ships

// From the accepted proposal; it does not compile until the change ships.
type SignerV2 interface {
	PublicKey() PublicKey
	Sign(rand io.Reader, data []byte) (*Signature, error)
	SignContext(ctx context.Context, rand io.Reader, data []byte, algorithm string) (*Signature, error)
	Algorithms() []string
	Signer() (crypto.Signer, error)
}

func NewSignerV2(signer crypto.Signer) (SignerV2, error)
func NewSignerV2WithAlgorithms(signer SignerV2, algorithms []string) (SignerV2, error)
func NewCertificateSignerV2(cert *Certificate, signer SignerV2) (SignerV2, error)

Nice to knowstdlib

Platform-independent SIMD in Go (Go blog)

What changed
The Go blog introduced the experimental simd package in Go 1.27: one portable API for vector operations, with types such as simd.Float32s, that uses AVX, AVX2 and AVX-512 on amd64, NEON on arm64 and wasm's SIMD instructions, and emulates the rest. It sits beside the architecture-specific archsimd package, which covered amd64 in Go 1.26 and added arm64 and wasm in Go 1.27.
Production impact
Both packages are experiments that build only with GOEXPERIMENT=simd, and this first release has gaps: there is no sum across a vector until ReduceSum arrives in the next release.
Try it
On Go 1.27, build the post's inner-product example with GOEXPERIMENT=simd go run . and compare it with a plain loop.
Source
go.dev/blog/simd-experiment
Explain it and run it

Understand it, then run it

SIMD means "single instruction, multiple data": the processor adds or multiplies several numbers at once, say eight pairs of float64 values in one instruction, instead of one pair at a time. It can speed up anything that does the same arithmetic over lots of data.

Until now, using it from Go meant writing assembly. The experimental simd package lets you write it in plain Go, once, and have it run on every platform: with real SIMD instructions where the processor has them, and emulated where it does not.

Run it now

Todaygo
package main

import "fmt"

// The Go blog's example for the experimental simd package is an inner
// product. This is the plain loop it vectorizes: one multiply and one add per
// element. With GOEXPERIMENT=simd on Go 1.27, simd.Float32s loads several
// elements at once and MulAdd handles all of them in one step; this sandbox
// builds without that experiment, so it runs the scalar version.
func innerProduct(x, y []float32) float32 {
	var sum float32
	for i := range x {
		sum += x[i] * y[i]
	}
	return sum
}

func main() {
	x := []float32{1, 2, 3, 4, 5, 6, 7, 8}
	y := []float32{8, 7, 6, 5, 4, 3, 2, 1}
	fmt.Println("inner product:", innerProduct(x, y))
}

What it printed when we ran it on Go 1.27.1

inner product: 120

Run sends this program (for Solidity, the contract and its tests) to our own sandbox, where it is compiled and run once, with no network, and what it printed or the test report comes back here. Nothing is kept. Runs are counted per visitor for the day so everyone gets a turn; the details are on the legal page.

Exercise

Under the new rule, string(i) with an int stops compiling. The starter uses it twice: once meaning the character A, once meaning the number 65. Change both so the program prints A and then 65, in a way that stays valid from Go 1.28.

Startergo
package main

import "fmt"

func main() {
	letter := 65
	count := 65
	// TODO: both lines use string(int), the form Go 1.28 removes.
	// The first means the character A, the second the number 65.
	fmt.Println(string(letter))
	fmt.Println(string(count))
}
Show a solution
Solutiongo
package main

import (
	"fmt"
	"strconv"
)

func main() {
	letter := 65
	count := 65
	fmt.Println(string(rune(letter))) // a character: say so with rune
	fmt.Println(strconv.Itoa(count))  // a number: format its digits
}

What it printed when we ran it on Go 1.27.1

A
65

Run sends this program (for Solidity, the contract and its tests) to our own sandbox, where it is compiled and run once, with no network, and what it printed or the test report comes back here. Nothing is kept. Runs are counted per visitor for the day so everyone gets a turn; the details are on the legal page.

The 60-second version

This week the Go team accepted three proposals. The biggest one tightens a conversion that has confused people for years: turning an integer into a string gives you a character, not digits. From language version 1.28, that only compiles for byte and rune values, and go vet already shows you every place that will need a fix. The second gives every test two goroutine labels, its name and which run it is, so profiles can tell you which test spent the time. The third folds the three signer interfaces of the SSH package into one, called SignerV2, and drops DSA keys. On the Go blog, the team introduced an experimental simd package in Go 1.27: one portable way to do vector arithmetic from plain Go, behind GOEXPERIMENT equals simd. There were no Go releases this week.

Written by gpt-oss-120b · claims checked against the sources · human-reviewed