Go 1.27 Interactive Tour
335 points - today at 1:35 AM
SourceComments
Could someone take the example, reduce it to a non-generic version for two types I DO understand, then show that with the new feature I can collapse them into the Box/Map example in the doc?
I have 10+ years of Go experience and I can't make heads or tails of "(b Box[T]) Map[U any](f func(T) U) Box[U]"
I really wish they didn't use such stupid LLM-isms.
llm generated
The math/rand/v2 package has a number of functions which return random numbers of a certain type:
i := rand.Int32() // a random signed 32-bit integer (type int32)
j := rand.Uint64() // a random unsigned 64-bit integer (type uint64)
It has functions which return a number within a range: in := rand.Int32N(10) // a random int32 in the range [0,10)
jn := rand.Uint64N(100) // a random uint64 in the range [0,100)
It also has a generic function, rand.N, where the return type is set by a type parameter. The definition of rand.Int32N (for comparison) and rand.N are: func Int32N(n int32) int32
func N[Int intType] (n Int) Int
Adding some spaces to make the common elements align (apologies if my formatting gets mangled), that's: func Int32N (n int32) int32
func N [Int intType] (n Int ) Int
As you can see, the generic function N has the same signature as the non-generic Int32N, except the type it operates on is set by a type parameter (named "Int"). The type parameter has a constraint, intType, which is a private type defined in the math/rand package. (There's nothing magic about this constraint, it's just a list of all the integer types in the language, and you can write it yourself if you want to. It's a separate type to keep the function signature of N from becoming too large, and it's internal to math/rand because it doesn't need to be part of the public package API.)The nice thing about rand.N is that it lets you write something like this:
// d is a random time.Duration in the range [0, 10 minutes)
d := rand.N(10 * time.Minute)
Without generics, you'd instead write this as the following, which is a lot more noise: d := time.Duration(rand.Int64N(int64(10 * time.Minute)))
The generic rand.N has a more confusing type signature and a lot more language complexity behind it, but the code using it is simpler and easier to read. We think that's a good tradeoff, but of course not everyone will agree.All the functions I've mentioned so far use a default random number source. Each of them also exists as a method of the rand.Rand type, which generates numbers from a user-provided randomness source. For example:
rng := rand.New(rand.NewChaCha8(seed))
a := rng.Uint64()
b := rng.Uint64N(100)
There is one exception, though: Until Go 1.27, there was no Rand.N method, because we did not support generic methods. (A generic type could have methods, but those methods could not be further type parameterized.)In Go 1.27, there is now a Rand.N method:
// Using a ChaCha8-based source with a defined seed,
// generate a duration in the range [0, 10 minutes).
rng := rand.New(rand.NewChaCha8(seed))
d := rng.Duration(10 * time.Minute)
This method's signature is: func (r *Rand) N[Int intType](n Int) Int
Comparing function vs. method and generic vs. concrete: func Int32N (n int32) int32 // function
func N [Int intType] (n Int ) Int // generic function
func (r *Rand) Int32N (n int32) int32 // method
func (r *Rand) N [Int intType] (n Int) Int // generic method
In this case, generic methods permit us to fix a small wart in the package API. This example isn't the motivating reason for adding generic methods, but I think it serves as an example of how adding them makes the language a bit simpler and more consistent. In Go, methods are just a type of function. Previously, you could write a type-parameterized function, but you couldn't write a type-parameterized method. That's an inconsistency that you need to remember. Now you can write type-parameterized functions or methods, using a consistent syntax for either.Type-parameterized methods don't participate in interface satisfaction, so this change isn't without its own subtleties. Discussing the tradeoffs there would double the length of this post, and weighing them is why it took so long for us to decide to add generic methods.
Another possibility is that people will use generic methods to write unreadably complex code. My personal opinion is that nothing will stop people from writing unreadably complex code if they want to; the fix to complexity is to not do that.
What would an implementation look like? Wouldn't it be quite different from the existing one because it has to rely heavily on indirection because (limited) monomorphimization is not possible?
They're probably useful, but clearly not sexy (as golang in general).
That's completely unreadable.
> The quieter but bigger change: the classic encoding/json (v1) package is now backed by the v2 implementation under the hood.
This is fantastic content nevertheless.
Does anyone have a bit of an inside view into what changed in the perspectives of the language maintainers?
Iâm not buying the âit took us 20 years to understand how to do it correctlyâ argument, as this is something you explicitly take into consideration when designing the language or not. And it was specifically not a part of language design, and is much harder to retrofit (backwards compatibility).
So what changed?
If you're taking a List[T] and all you want to do is to call list.size() then you don't care what type of list it is. In Java you can write a function which takes a List<?> but in Go you have to write List[T] so then the question becomes what is T? You have to make the function (or type you're a method on) generic. If you make the type generic then every user of your type also needs to specify T, etc.
I don't think it would be impossible to add that to Go. Allow List[?], which matches a List with any type parameter. Calling functions which don't involve the type parameter like list.size() would be fine, calling a method returning the type parameter like list.get(n) would return "any", and methods taking the type parameter like list.set(n, obj) would probably not be callable.