|
|
Subscribe / Log in / New account

Go 1.22 released

Go 1.22, the most recent version of the Go programming language, has been released. It comes with two language changes to for loops: a fix for a longstanding "gotcha" with accidentally sharing loop variables between iterations and adding the ability to range over integer values. There are also additions to the standard library, improved performance, and more. See the release notes for further information.

to post comments

Go 1.22 released

Posted Feb 7, 2024 17:31 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (28 responses)

Super nice. For me, the only major pain point remaining is the error handling.

Go 1.22 released

Posted Feb 7, 2024 22:11 UTC (Wed) by milesrout (subscriber, #126894) [Link] (13 responses)

It's never going to change. And that's a good thing. Not only is there no better way of doing it, but it would do something that, as far as I know, they've promised not to do: render most Go code that exists unidiomatic.

Go 1.22 released

Posted Feb 7, 2024 22:13 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

> they've promised not to do

Nope. No such promises were made.

And error handling in Go is just fugly.

Go 1.22 released

Posted Feb 8, 2024 12:28 UTC (Thu) by milesrout (subscriber, #126894) [Link] (2 responses)

Of course. You're right, we should just replace it all with WASM or something right? </eyeroll>

Go 1.22 released

Posted Feb 8, 2024 13:09 UTC (Thu) by timon (subscriber, #152974) [Link] (1 responses)

Oh come on, this comment is markedly sub-standard for LWN.

Go 1.22 released

Posted Feb 8, 2024 20:24 UTC (Thu) by milesrout (subscriber, #126894) [Link]

What's markedly substandard for LWN is Cyberax's notorious and unending trolling.

Go 1.22 released

Posted Feb 8, 2024 1:47 UTC (Thu) by pj (subscriber, #4506) [Link]

>Not only is there no better way of doing it,

[citation needed]

...for instance, I think Zig does it better, though in the end it's a matter of opinion.

Go 1.22 released

Posted Feb 8, 2024 11:58 UTC (Thu) by georgm (subscriber, #19574) [Link] (7 responses)

Why not add syntactic sugar for the most used pattern in go (lending from rust syntax):

if function returns "(foo, error)" or just "error":
a "?" on a function which returns (bar, error) would return (default, error) or only error on error
example: file := os.Open("file.txt")?
a "?" on a function which return only error woud return (default, error) only error on error
example: os.Mkdir("testdir", os.ModePerm)?

You are not forced to use this pattern, so both the current and the new style would be fine and can coexist

Go 1.22 released

Posted Feb 8, 2024 12:26 UTC (Thu) by milesrout (subscriber, #126894) [Link] (6 responses)

Because it's ugly and unidiomatic, or it renders existing code unidiomatic. It means there are two ways of doing things where before they was just one. Either that creates inconsistency, or people pick one or the other as the "preferred" or "best practice" way of doing things - and then it means you separate the world into "old legacy" code and "new" code. This has lots of unfortunate flow-on effects. For example, it would encourage people to rewrite existing code to be more "modern" (for no good reason - the old code works fine). But it's also just distasteful. The nice thing about 'Go' is that code ages pretty gracefully compared to languages that constantly churn new syntactic sugar/library sugar into the language. C++ is notorious for this: you can pretty much tell what year some C++ code was written as soon as you look at it, once you know what to look for. 'Oh yeah, make_shared but it's still using make_unique, that's likely to be 2014ish'.

I think the worst part of it, though, is that it creates special-case syntax for what is should just be general programming. Go supports multiple return values and sometimes those return values include errors. Sometimes they don't. The nice thing about programming languages is that they are general. You use the same simple mechanisms for everything. Go is particularly good about this, almost as good as C. In C, almost _everything_ you do is arithmetic, dereferencing, taking the address of something, for/while/if/switch or calling a function. In Go you can add '<-' and 'go'.

Why add new syntax to do something that is so simply achieved with a simple 'if' statement? It reminds me of people suggesting that we all need to use 'map', 'filter' and 'reduce' functions and complicated iterator schemes. Do we? What's wrong with a 'for' loop with a nested 'if' structure controlling a line of code that modifies a variable? More than sufficient, I think. Just to save a few characters? Making code more dense isn't necessarily a good thing. Error handling in Go gives your code a nice bit of breathing room, I think. Code in more dense languages just needs more blank lines to space it out, IMO.

Basically, I think syntactic sugar is generally bad.

Go 1.22 released

Posted Feb 8, 2024 13:15 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

> Because it's ugly and unidiomatic, or it renders existing code unidiomatic.

This argument is as stupid as anything. "We can't fix stuff because fixing stuff would make our old stuff look bad". So there.

Go 1.22 released

Posted Feb 8, 2024 13:25 UTC (Thu) by Funcan (subscriber, #44209) [Link] (1 responses)

It's not "we can never fix anything" and more "we take old cold into account when making a decision whether to fix something, and we weight the value of keeping old choice idiomatic fairly heavily".

There's the change in this release for variable reuse in for loops, which removes the need for an ugly idiom in new code, but which will have to stay in coffee intended for older compiler versions, for example.

Adding genetics also meant that some old code became less idiomatic, but it was considered a big enough win.

Go 1.22 released

Posted Feb 8, 2024 21:29 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]

New handling will (likely) just add a shortcut for the old idiomatic code. Nothing will change behind the scenes, the old code will still be perfectly fine.

It might eventually start _looking_ strange to new Go developers, but that's already true for other kinds of code. For example, very old Go code had no `context.Context` (so it was not interruptible) and even used "goto err" pattern (see: https://cs.opensource.google/go/x/net/+/master:websocket/... ).

Go 1.22 released

Posted Feb 8, 2024 20:23 UTC (Thu) by milesrout (subscriber, #126894) [Link]

That's not the argument at all. The argument is that they shouldn't "fix" it because it's not broken in the first place.

Go 1.22 released

Posted Feb 8, 2024 13:35 UTC (Thu) by georgm (subscriber, #19574) [Link]

> Because it's ugly and unidiomatic

The "ugly" depends on personal preference.

Now with type "any" - you have that split already. Or should we continue using interface{} all over the place to not have new and legacy code? And generics?

> You use the same simple mechanisms for everything. Go is particularly good about this, almost as good as C

Yes, as good as C to not bring any safety measures or syntax help:
You need a map: use map[foo]bar

Wait? You have to "make(map[foo]bar)" (or map[foo]bar{}) before using it (but slices are okay to not exist, simple).

Wait? You want to use it in parallel go routines (not even in our code, but for example the http router)? Don't use it. It is unsafe, or (like in general in go) - add a separate lock to use it.

But you could use sync.Map for this. But then forget about the types (even if go supports generics by now).

Simple might be too simple or too error-prone. No one is hindering you on using this and nobody complains (until something crashes).

Just one example of "simple". There are a lot of other rough "simple" edges.

Go 1.22 released

Posted Feb 12, 2024 13:15 UTC (Mon) by mathstuf (subscriber, #69389) [Link]

> suggesting that we all need to use 'map', 'filter' and 'reduce' functions and complicated iterator schemes.

Because these functions separate the "what" from the "how" nicely. I don't have to seek out an extra or missing `!` or half-done modus ponens bug hidden in an `if` condition and change my thoughts based on whether the body contains `break` or `continue`. Instead, I get `until`, `while`, or `filter` as a name. Are we mutating the container? The values? Building a new structure or just making a pass over it? These are the kinds of questions Rust's `Iterator` trait methods answer for me directly. Then I can focus on *what* the code is doing overall without having to squint through a haze of partially-remembered open-coded intro-CS-course algorithm loops.

C++ also does it OK, but they don't compose all that well. Ranges does better but I've not had the chance to use them in anger yet.

Go 1.22 released

Posted Feb 8, 2024 13:14 UTC (Thu) by michaelkjohnson (subscriber, #41438) [Link] (13 responses)

The amount of nonsense spewed over Go's excellent choice to disambiguate errors (via the type system) from exceptions (panic) continues to amaze me.

This is, for example, something that Python got fundamentally wrong (see os.exists and the many wrappers written to not take the expensive exception case for file does not exist, which is properly neither an error nor an exception), and when I learned Go I was delighted that Go did not perpetuate this particular class of design error.

Go error handling is an example of the finely-tuned design sense that its creators brought to the language.

Explicit is better than implicit, and errors are not exceptions. Anyone who thinks errors are exceptional hasn't been paying attention. ☺

Go 1.22 released

Posted Feb 8, 2024 13:21 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link] (12 responses)

> The amount of nonsense spewed over Go's excellent choice to disambiguate errors (via the type system) from exceptions (panic) continues to amaze me.

I'm not talking about panics, the modern consensus is that exceptions are a bad idea. Go has some strange ideas about `recover`, but whatever, it's at least reasonable.

I'm talking about `if (err != nil) {return nil, err;}` statements that can easily take up half of all the code in functions that call a lot of other functions. They are unergonomic, and they prevent chaining.

Go 1.22 released

Posted Feb 8, 2024 14:06 UTC (Thu) by farnz (subscriber, #17727) [Link] (4 responses)

And given that this is a common pattern in existing Go code, why not have a special syntax that implements this form of error handling for you? Something that converts:


varFoo, err := GetFoo()
if err != nil {
    return err
}

into a shorter form - like varFoo := GetFoo() orReturnErr, maybe using a sigil for orReturnErr?

Go 1.22 released

Posted Feb 8, 2024 20:27 UTC (Thu) by milesrout (subscriber, #126894) [Link] (2 responses)

Because shorter is not necessarily better, and syntactic sugar is not automatically good. Brevity is the soul of wit, but it is not the soul of programming. It's more stuff to learn, and the language is already general purpose. It doesn't need an extra way of doing something it can already do! You are conditionally returning from a function. That's simply described: a conditional return. So conditionally (if) return (return). That's how programming works. You don't need syntactic sugar for every combination of control operators you think up.

Go 1.22 released

Posted Feb 9, 2024 10:17 UTC (Fri) by taladar (subscriber, #68407) [Link]

Shorter is better if shorter means you can see at a glance that the hundreds of copies of identical code are actually identical instead of having 99 identical copies and one that is different.

Go 1.22 released

Posted Feb 9, 2024 10:34 UTC (Fri) by farnz (subscriber, #17727) [Link]

The point of brevity in a programming language is to make doing the right thing easier than doing the wrong thing. If my "happy path" (the path I take if there are no errors) is hidden away in a sea of error handling, then it becomes hard to see whether or not the happy path does the right thing, because I've got to read my way around the error handling in order to actually see the happy path.

If you come up with a simple way to do the right thing, people will use it. Else, you have to keep an eye out for accidental mistakes - e.g. typing fooR, fooE := getFoo(); if fooR != nill { return fooE; }; sort of thing, where I'm using ; to mark newlines.

Go 1.22 released

Posted Feb 8, 2024 21:21 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]

I'm toying with writing a simple pre-compiler to try and translate "?" into "if err != nil...". This is really easy, because Go doesn't use "?" right now for anything (there's no ternary if) so the grammar will stay unambiguous.

I tried some manual experiments, and the resulting code looks _so_ _much_ _more_ _readable_.

Go 1.22 released

Posted Feb 8, 2024 14:13 UTC (Thu) by michaelkjohnson (subscriber, #41438) [Link]

Fair! You might even say that the pattern encourages developers to raise errors as panics when implement chaining; an anti-pattern.

(It's also possible to embed errors instead and have explicit handling in chains, so they don't exactly *prevent* chaining, but I agree that it is reasonable to argue that this is not the ergonomic best implementation of chaining.)

So my complaint isn't related to your concerns, and I appreciate the clarification!

Go 1.22 released

Posted Feb 9, 2024 5:46 UTC (Fri) by quotemstr (subscriber, #45331) [Link] (3 responses)

> the modern consensus is that exceptions are a bad idea

No it isn't. There's a small, vocal community that says so, but that doesn't make it right, and there are plenty of new exceptional systems being made.

Go 1.22 released

Posted Feb 9, 2024 14:27 UTC (Fri) by farnz (subscriber, #17727) [Link] (1 responses)

Out of interest, are they avoiding the two big problems I have with legacy exception-based languages:

  1. It's impossible at the call site in (at least) Java, C++ and Python to see whether a given call might throw an exception; I have to look at the function signature in Java or C++ to see if there's a suitable "noexcept" type marker (or lack of a list of exceptions, in Java's case). This one bites me regularly, since I get people showing me their code that's failing, and telling me that it can't fail in that way - inevitably, when I get a backtrace out of them, it shows that they're calling something that's throwing an exception when they thought they were calling something infallible.
  2. Maintaining checked exception specifiers is a chore for mid-level libraries (it's fine if you're a foundational library like the standard library); I can't just say "my exception specifier is that of readFoo plus that of parseRecord plus my own exception type", but I have to go and see what the exception specifiers of those functions are and copy-and-paste it across. And every time their exception specifiers change, I have to see if that affects me or not - can I remove a no-longer-needed specifier, do I need to add a specifier?

Go 1.22 released

Posted Feb 9, 2024 15:55 UTC (Fri) by michaelkjohnson (subscriber, #41438) [Link]

So-called "Exceptions" as used in many languages that conflate (expensive) exceptions with (cheap) normal errors are not a thing at all in Go. Instead, Go has a panic/recover system and an error system.

The Go "Error" type is an interface that fits into Go's normal type system:

https://go.dev/blog/error-handling-and-go

For an understanding of panic and recover, see:

https://go.dev/blog/defer-panic-and-recover

Go 1.22 released

Posted Feb 9, 2024 16:17 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link]

Can you point out a couple of recent examples? Zig essentially uses algebraic types, same for Rust, Go uses multiple return values, even C++ is also adding algebraic types for error handling.

Go 1.22 released

Posted Feb 10, 2024 15:43 UTC (Sat) by mathstuf (subscriber, #69389) [Link] (1 responses)

> I'm talking about `if (err != nil) {return nil, err;}` statements that can easily take up half of all the code in functions that call a lot of other functions. They are unergonomic, and they prevent chaining.

This is bad anyways. Making sugar for bad idioms will only rot your teeth (same as blind `?` error bubbling in Rust). What is missing is the context that made the error happen. I find "file not found" from a high-level API useless. "Git repository expected; no .git found" is actually useful. Better as an enum so that code can inspect and adapt instead of low-level code assuming that a pre-baked string for user consumption is the only suitable representation.

Go 1.22 released

Posted Feb 10, 2024 20:37 UTC (Sat) by Cyberax (✭ supporter ✭, #52523) [Link]

Yep, it's extremely frustrating when you get something like "IO error" in your logs without any details or a stacktrace. Go has finally grown a standardized way to wrap errors, so you can somewhat easily preserve the chain of failures like this: "failed to call func1 -> failed to open HTTP connection -> failed to resolve DNS name -> IO Error".

Now Go needs to work to make it more idiomatic.

Go 1.22 released

Posted Feb 7, 2024 19:51 UTC (Wed) by rbranco (subscriber, #129813) [Link] (1 responses)

Please include the Golang Weekly Newsletter in the LWN Weekly Edition like you do with other languages. :)

https://golangweekly.com/

Newsletter

Posted Feb 7, 2024 19:52 UTC (Wed) by corbet (editor, #1) [Link]

It looks like they have a nice RSS feed ... yes, we'll add it, thanks.

Loop variables shared between iterations - not just in Go

Posted Feb 7, 2024 22:21 UTC (Wed) by milesrout (subscriber, #126894) [Link] (1 responses)

>a fix for a longstanding "gotcha" with accidentally sharing loop variables between iterations

This is a classic issue. When I was just a beginner, I stumbled into the same problem in Python. A callback being constructed in a loop (maybe a list comprehension?) using 'lambda', which captures the loop iterator. The solution is clever: instead of [(lambda: i) for i in range(10)], you can do [(lambda i=i: i) for i in range(10)]. The default argument is evaluated when the lambda expression is evaluated. It looks odd, but it works.

>>> l1 = [(lambda: i) for i in range(10)]
>>> l2 = [(lambda i=i: i) for i in range(10)]
>>> [f() for f in l1]
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
>>> [g() for g in l2]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Loop variables shared between iterations - not just in Go

Posted Feb 8, 2024 0:33 UTC (Thu) by iabervon (subscriber, #722) [Link]

That looks less odd than what I'd have thought of, which is:

[(lambda i: lambda: i)(i) for i in range(10)]

But I think I prefer:

def make_callback(i):
    return lambda: i

[make_callback(i) for i in range(10)]

The fact that you're creating a list of functions that are different due to capturing different values is worth emphasizing by making it a named function.


Copyright © 2024, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds