You guys can keep complaining about how go is too verbose, but I love everything about go.
I love the error handling, I love the forced formatting, i love all the linting it has including style guides. When you read other source code it's so easy to understand it and make sense of it. Thank you go team
(Ok, maybe I am a bit sceptical with the latest generic additions, but overall it's a great language. I love it.)
Oh yeah totally agree. I don't understand why someone would want to write `slices.Contains(s, needle)` when you can write this beautiful poem like a Shakespeare in VSCode:
found := false
for _, v := range s {
if v == needle {
found = true
break
}
}
Oh and I totally want to build a stack trace manually. It's like doing cardio to me.
if err != nil {
return fmt.Errorf("my function name but in spaces: %w", err);
}
This is very elegant by the way, so that we need errors.Is now, which has to dynamically check if the error implements Unwrap() error or Unwrap() []error. Because having any language facilities for error handling is harmful.
found := false
for _, v := range s {
if v == needle {
found = true
break
}
}
Do you see it? It copies the v into a local variable, which could be tremendously wasteful if it's a large struct. You should instead be taking a pointer to s[i] and comparing the value there with `needle`.
If you'd used `slices.Contains(s, needle)`, on the other hand, it could have such a performance bug in it and you'd never know.
> on the other hand, it could have such a performance bug in it and you'd never know.
wut...? the debate isn't between closed (incompetent) source and open (competent) source - the debate is between verbose language and expressive language.
> If you'd used `slices.Contains(s, needle)`, on the other hand, it could have such a performance bug in it and you'd never know.
Perhaps you're much better at programming than I am, but I prefer these semantics in a language because I figure they're much more likely to have been optimized empirically, support vectorization, and be less buggy than another rote loop I'm trying to speed through.
That is just a silly rationalisation by primitivists, and they will never be able to articulate any rational point where abstracts makes sense, in their world, whatever Russ Cox and a bunch of Google employees deem fit is the ideal.
Even when Russ Cox and Go team had come to term with reality and stopped pretending like we are in 70s still, these lot will move goalposts. It is a kind of psychosis and sycophancy that is beyond rational discourse.
I don't say it's perfect, but right in this moment I feel most comfortable with go and I don't mind jumping through a few hoops or writing things multiple times
The more common steelman for go is "most programmers are idiots, so a language designed for idiots is a good tradeoffs since it's easier to hire programmers for a codebase in that language"
I think that one is true. Like, if you're building something that is able to be successful despite having a poor type system, frequent panics, and difficult to correctly use concurrency primitives, Go is a great language for letting the lowest common denominator programmer be productive.
If you're building more serious software, then it can be a very bad tradeoff that destroys your company or product, but you know, that's true of a bunch of languages.
> Because having any language facilities for error handling is harmful.
No, making error handling verbose mandatory is meant to make developers do mental cardio and be mindful of what they are doing.
You can either keep developers mindful or punish them after they make a mistake by shouting at them and make them waste their time during re-compiling.
P.S.: Yes, I love Go's error handling mechanics, which makes handling errors mandatory, not optional, and if you ignore the error, this is a deliberate choice and the burden is on the developer.
Go does the former, Rust does the latter.
What are you talking about, only Rust actually forces you to handle errors. Go functions merely return a tuple with an error along with the result, with a convention that you must checktror a non-nil error before using the result.
Rust bakes this into the type system, a function can truly return a result or an error.
While I have my opinions on manual memory management, I'll not open that file today (well, it may leak).
On the other hand, Go explicitly warns and tries to prevent you from ignoring or not handling possible errors. This is a bit different than a happy C compiler which doesn't warn you about leaking memory.
> So, you handle the error in the end, or forcefully and intentionally ignore it. Again, if the code goes boom, it's on the developer, not on Go.
Except Go doesn’t actually require you to handle the error. You can forget to handle the error, or forget to do a nil check. And Go won’t tell you until it crashes and explodes at runtime.
Technically the user’s fault, but good systems protect the users from their own mistakes.
You used "_" to ignore the error variable, which I call "IDGAF" placeholder.
So, you willingly ignored the error and tell me that you don't have to check the error? You told the compiler that you don't care about the error explicitly (via "_"). That's on you then.
In my first comment I noted in the P.S. section:
...if you ignore the error, this is a deliberate choice and the burden is on the developer.
You used "_" knowingly. Compiler/linter didn't add it there by itself.
I mean, do you even read the language documents to understand how a language works?
Are you really being so obtuse? Of course in real code you might use the error. For printing, forwarding it to a logging frame or anything else. The point is the error does not guard access to the return value. Here basically the same example:
package main
import (
"errors"
"fmt"
)
func getMessage() (string, error) {
return "DO NOT INSPECT", errors.New("something went wrong")
}
I have commented inside the code, but to recap here:
- If you don't declare err variable, the code won't compile.
- If you don't use err variable, the code won't compile again.
So, you need to both declare and use the err variable to be able to compile the code. So you can't forget. Your code will not compile.
The only way to "forget" is to declare err as "_", which I call IDGAF placeholder, and this is a deliberate choice to ignore that variable. So you willingly and knowingly ignore the error variable.
Otherwise Go won't give you Go ahead.
Seriously, try to compile the example I have given. It's fresh, so hold with mittens.
It seems like an innocent mistake to me. I think there's a difference between actively ignoring something (like assigning an error to _), and ignoring something through omission. I think computers should try and prevent mistakes.
Go errors if you try to assign a number to a string, so it's clear there is some intention for the machine to catch when your brain makes a silly mistake.
I don't know how other developers think while coding, but if something can error out, I always handle the error first, or at least insert a panic() or equivalent immediately before continuing coding, regardless of the language I'm working on.
I can also think a couple of cases where I deliberately catch the error, but don't do anything on it explicitly, esp. if I'm talking with a buggy hardware. I'd still log the errors as INFOs or WARNs though. I have seen too many "task failed successfully" errors in my life.
We can have the same argument about memory unsafe languages: "it should be the programmer that checks that the memory it's accessed in a safe way and not the machine"... and yet after many years and legions of programmers not being able to stop themselves creating memory leaks, we created memory safe languages and we advocate for their usage.
I hope it's clear that what was proposed above is just a toy problem to show the issue. In complex parts of the code, unfortunately, these errors sometimes happen. That's a fact.
A programming language should actually try to protect you from some logic errors. This is why have we programming languages in the first place. Otherwise why bother with garbage collection, types etc? Should we just use assembly then for maximal flexibilty and putting all the burden on the programmer?
I agree with you that it would have been nice with Result and option types in Go.
But as someone else pointed out, unhandled errors are very rare in practice in Go because every Go project tends to use static code analysis tools that catches this. When people talking about things blowing up during runtime I can’t help but think that they can’t be serious Go users.
On every build I generally run vet, lint (revive), staticcheck, gosec and test. And that’s the “light” build that doesn’t run leak analysis, fuzzing, race testing, benchmark regression and integration tests in addition. The light build is still faster than the Rust compiler. I haven’t compared the heavier build.
When people pretend the absence of features is a huge problem, I tend to think that these are people who either aren’t regular Go users or perhaps they are more interested in debating languages than writing code.
I'm actually not all that fussed about Result or Option types. I don't mind Go's approach to error handling. I think it should just have language/analysis features built it to ensure that you do it 'correctly', so things don't blow up if you do not remember a part of the system ("this value is sometimes nil") or you forget to do something.
The fact that external tools exist that "every Go project tends to use" to fill common a gap in the type system indicates to me that maybe the language itself could be improved.
Look at exceptions in Java, for instance. In theory the mechanism is there to guarantee that errors will be handled. In practice there are multiple schools of thought — several of which purposefully circumvent this mechanism by throwing runtime exceptions because they think checked exceptions just leads to a lot of unnecessary work.
It has been too long since I used Java to remember all the arguments for and against. But I do remember trying multiple approaches and realizing that only using checked exceptions turned out to be a bit too inflexible. That there were legitimate reasons to use runtime exceptions.
Go’s type system is probably “good enough”. Sure, I’d love to have option types and results. And it would have been nice to move some of the things in vet, lint, staticcheck into the compiler. But in _practice_ separating them is actually beneficial. Because it allows you to trade build speed for security. For instance when you are making a tiny change and you just want to make sure it still builds. That’s a _practical_ tradeoff you could not have made if the compiler were always strict.
One thing I wish Go would have adopted is Javadoc-like comments with explicit markup to document parameters, return values and possibly panics. Those were _more_ useful in practice than exceptions in Java because you could force people to be more deliberate when designing “contracts”. When I did Java we used to have the build fail if classes and methods did not document all params, return values, exceptions etc.
I also taught people to write unit tests while _only_ looking at the Javadoc for what they were testing and notating at the code. Even after years of doing this, I’d regularly find that implementations didn’t behave as documented when writing tests. This made people care about interfaces/seams/APIs/contracts and spend less time obsessing over having the compiler save them from sloppiness. It put the developer in the driving seat rather than have them run behind the compiler and just nudging things into a state where it compiles. It helped people think more about why something failed from a design point of view rather than put all their faith in the tooling.
I think that’s the biggest mistake of Go: it doesn’t care about documentation and to the extent it does, it introduces really, really stupid, pointless formal rules that does nothing to help developers.
Rust is neither the only nor the first language to contain a result type (https://en.wikipedia.org/wiki/Result_type). Definitely a much nicer way to handle errors though.
In practice you use Go with a linter, so this is not a problem in practice. I have never seen a missed error.
The problem is painstakingly and manually having to build the stack trace, and then unwrapping it few layers above if you ever need to check what error it was.
Good point, but I personally never do that? Instead of doing (expensive) calls, I just assign the result to a variable and use that instead. Accessing to memory is pretty cheap in Go, and it's cleaned once it goes out of the scope, so why not?
Considering some expensive Go calls do the same caching underneath, this is the preferred design pattern, I assume.
I am skeptical about Go's error handling, but there are cases where it is desirable to return both a result and also an error, like returning what can be returned while warning users about any errors. That can be modeled in Rust and other languages as well, though it is the default for Go.
Unpopular take, but I personally also believe that unchecked exceptions in Java and other languages like that are an issue - because it makes too easy to miss where things could go wrong, instead of making you visibly handle the potential known failure points, or indicate to callers what you're not handling.
This comment could make the same point and be even more effective if wasn't written as a snarky retort in violation of several of the guidelines of the site ( https://news.ycombinator.com/newsguidelines.html )
Also, if error wrapping hurts you so much (I don't use it), just implement a project-specific error that works how you want. This could be something that is JSON serializable, that captures a line number at each return site, etc. It will take like 10 minutes to get your project's errors working exactly how you want.
My projects usually do -
log.SetFlags(log.LstdFlags | log.Lshortfile)
// ...
if err != nil {
log.Println(err)
return errors.New("Error doing the thing.")
}
That essentially logs a stack trace with line numbers up the whole error chain, each return adding the outer context. I only ever use errors.Is for os.ErrNotExist.
I know about slices module. But GP is not happy about generics apparently.
I think your error solution is good. But still it's a poor imitation of Exceptions wherein you have to capture the stack trace manually, possibly incuring a perf hit (haven't measured), and write handling code in every layer. Made worse by the fact that people return desperate errors for even precondition violations which should be panics.
1. There is nothing stopping you from implementing a Contains function.
2.That error handling is one of the best features. It makes me explicitly acknowledge the errors instead of letting them just happen. No error goes unnoticed!
Each if err != nil is an explicit reminder to check, do I need to clean up? Do I need to log this error to a file?
"And no more oh an error happened I wonder where"
With LLMs verbosity is not an excuse anymore. Just generate it and focus on other things then
Even if you prefer manually checking for errors after every call that might fail, I fail to see how one can love go’s verbosity. Compare go’s
foo, err := bar()
if err != nil {
return ERR;
}
with something like (hypothetical)
foo := bar() ||| return ERR;
where the compiler, seeing that bar returns an Either<int,err> can enforce the presence of the ||| clause or, alternatively, require later code to check for errors if the ||| clause isn’t present. I think that’s both more robust (prevents one from forgetting to check for errors) and shorter (allowing for showing a lot more code on a screen or page)
There are so many proposals for improving errors, even half implemented ones that are dropped. I even stopped checking pros and cons after a few. As ultimately everything got rejected and the message is that you have to live with verbose checking with llms now.
I think the examples are both bad along different axis. One occupies more lines, the other results in _more_ and uglier syntax. They’re both bad. So the question is which axis is worse.
While the first occupies more lines I suspect I’d spend less time spotting it in the code. The physical shape of the expression is something my brain is used to seeing after 40 years of programming. Even when code grows more complex.
The second is OK when it stands alone, but I am not so certain it would be as easy to spot in more complex surroundings. Even on a single line, there is something a bit ugly about the code. Even before we get to the desperate triple-pipe. That kind of reminds me of the desperate attempts at repairing JS after they realized its equality semantics were shot. Just heap more characters on it.
The thing is: it is actually very hard to judge how ergonomic a language is by just looking at it. You have to use it and you have to use it enough to realize where the paint points really are. I’ve read through a lot of the responses in this discussion and I’ll be honest: I think a lot of people who criticize other languages (be they Rust, C, Go or whatever) aren’t very fluent in them. It takes a couple of years to develop fluency.
I love how during prototyping, the compiler will tell me off for having an unused variable and fail to compile. I totally love the idea of crashing when writing on a closed channel.
I do love the batteries included attitude. Having benchmarking included as well as a mechanism to run tests to check (test) for race-conditions right from the language/build tool is amazing.
My only wish is that go could become less verbose. There are several frontends to go that are more compact, compile down into golang, and then let you enjoy all the benefits.
100% with you on every point. It's the only language I've worked in that lets me read other codebases without an hour or two of "wut?" happening, so they did something right!
Kubernetes is a behemoth, but the Docker (Moby) source code is actually pretty easy to get into (if you're already a Docker user and understand how containers work)
The point is in Go it's pretty hard to write code so ugly that it'll be hard to read. I'm actually quite confident I could quickly onboard on almost any Go codebase (Kubernetes might be an exception). When I used to work with Java, I very rarely even tried to read the source code of my dependencies because it was unreadable.
Yes, I find the K8s codebase pretty easy to read actually, but I am biased since I work with K8s and the codebase makes a lot of sense when you understand what everything does already.
When I moved from Kotlin to Go because of a job change. it was painful. Apart from its runtime benefits, it is overrated IMO. it lacks basic conveniences. Go is not a simple language, it is a primitive language.
I think that the best feature that go was able to create is a very strong community around the language, where practices are as important as the language itself. Maybe these practices are what makes the community strong?
Also not adding every single feature that could be nice is a good thing to me. Yes, there is a lot of syntactic sugar you could add, but this just ends up in even more debates and in the end it doesn't matter that much. There is a very clear path how to achieve regular things in go and that works fine for now
Someone in the thread about the Ruff update ingenuously said they wished Go had something like Ruff, being unaware of this framework. In that thread, someone seemed to take it as a sign that many people who might care to know about this don't yet, so they made a dedicated post for it.
For SpiceDB[0], we've found a lot of success using this framework to define our own analyzers; it's probably 10x easier now with LLMs. No need for tribal knowledge or more time wasted on code review if you can just turn it into a linter and move on.
The Go team's emphasis on tooling in the service of software engineering is a boon to human and agentic development alike. Give yourself and your agents great tools.
An example from one of my recent projects: https://github.com/verdverm/gmd/blob/main/Makefile (give agents simple "tool calls" instead of needing to divine the correct args/flags every time, essentially invocable agents.md content)
One of the interesting things to call out from this is using build tags for testing { unit, coverage, recorded, real api }, with the buffet allowing the agent to iterate faster and more targeted. I tend to run the linting and coverage in a new session, have a report generated, and then another fresh session to start dealing with gaps.
the most valuable thing in this article for me was this:
The early loop looked like this:
/goal improve the perf by 20%
-> a great deal of plausible code
-> a confusing benchmark
-> another plausible patch
Later it looked like this:
find the expensive work
-> explain why it happens
-> change one mechanism
-> compare with the previous Rust revision
-> test the complete application
-> retain, revise, or reject
hoppp | a day ago
jamescun | a day ago
You can see it's used by _a lot_ of linters already:
https://pkg.go.dev/golang.org/x/tools/go/analysis?tab=import...
tgv | a day ago
stephbook | a day ago
b7e7d855b448 | a day ago
I love the error handling, I love the forced formatting, i love all the linting it has including style guides. When you read other source code it's so easy to understand it and make sense of it. Thank you go team
(Ok, maybe I am a bit sceptical with the latest generic additions, but overall it's a great language. I love it.)
jerf | a day ago
ncruces | a day ago
Which is a great way to make sure they're not overused, which in my experience is better than underuse.
wannabe44 | a day ago
logicchains | a day ago
If you'd used `slices.Contains(s, needle)`, on the other hand, it could have such a performance bug in it and you'd never know.
mathisfun123 | a day ago
wut...? the debate isn't between closed (incompetent) source and open (competent) source - the debate is between verbose language and expressive language.
kajman | a day ago
Perhaps you're much better at programming than I am, but I prefer these semantics in a language because I figure they're much more likely to have been optimized empirically, support vectorization, and be less buggy than another rote loop I'm trying to speed through.
asdf88990 | 19 hours ago
Even when Russ Cox and Go team had come to term with reality and stopped pretending like we are in 70s still, these lot will move goalposts. It is a kind of psychosis and sycophancy that is beyond rational discourse.
pjmlp | 14 hours ago
boredatoms | a day ago
irq-1 | a day ago
https://cs.opensource.google/go/go/+/refs/tags/go1.26.5:src/...
msie | a day ago
b7e7d855b448 | a day ago
TheDong | 23 hours ago
inigyou | 22 hours ago
TheDong | 18 hours ago
I think that one is true. Like, if you're building something that is able to be successful despite having a poor type system, frequent panics, and difficult to correctly use concurrency primitives, Go is a great language for letting the lowest common denominator programmer be productive.
If you're building more serious software, then it can be a very bad tradeoff that destroys your company or product, but you know, that's true of a bunch of languages.
yoghurtwere | 16 hours ago
Rust async has a bad reputation in Rust circles, due to difficulties like deadlocks and poisoning (also regarding the messy panic system Rust has).
> Rite of passage for a Rust developer is creating a deadlock through an if-statement.
inigyou | 11 hours ago
bayindirh | a day ago
No, making error handling verbose mandatory is meant to make developers do mental cardio and be mindful of what they are doing.
You can either keep developers mindful or punish them after they make a mistake by shouting at them and make them waste their time during re-compiling.
P.S.: Yes, I love Go's error handling mechanics, which makes handling errors mandatory, not optional, and if you ignore the error, this is a deliberate choice and the burden is on the developer. Go does the former, Rust does the latter.
sapiogram | 23 hours ago
Rust bakes this into the type system, a function can truly return a result or an error.
bayindirh | 23 hours ago
So, you handle the error in the end, or forcefully and intentionally ignore it. Again, if the code goes boom, it's on the developer, not on Go.
> Rust bakes this into the type system, a function can truly return a result or an error.
Error being a variable or baked into the type system doesn't change the practical result. You must handle the error or purposefully ignore it.
> only Rust actually forces you to handle errors.
When you have two programming languages which makes you handle the error, the word only becomes a little invalid.
Semantics doesn't change the result. You have to acknowledge and act on the error either way.
resonious | 22 hours ago
This is the same argument C (and Zig!) people have for manual memory management. You can avoid memory problems by being a good developer.
yoghurtwere | 16 hours ago
stefr- | 14 hours ago
bayindirh | 10 hours ago
On the other hand, Go explicitly warns and tries to prevent you from ignoring or not handling possible errors. This is a bit different than a happy C compiler which doesn't warn you about leaking memory.
madeofpalk | 20 hours ago
Except Go doesn’t actually require you to handle the error. You can forget to handle the error, or forget to do a nil check. And Go won’t tell you until it crashes and explodes at runtime.
Technically the user’s fault, but good systems protect the users from their own mistakes.
majormajor | 17 hours ago
Especially as someone who's read a lot of code written by newcomers to Rust.
yoghurtwere | 16 hours ago
https://www.reddit.com/r/programming/comments/1p0srgs/commen...
rowanG077 | 14 hours ago
bayindirh | 14 hours ago
rowanG077 | 13 hours ago
``` package main
import ( "errors" "fmt" )
func getMessage() (string, error) { return "DO NOT INSPECT", errors.New("something went wrong") }
func main() { msg, _ := getMessage() // Ignore the error. fmt.Println(msg) } ```
Compiles normally and prints "DO NOT INSPECT"
bayindirh | 13 hours ago
Don't make me spit my tea. That monitor is expensive. Of course, yes: https://git.sr.ht/~bayindirh/nudge
Jokes aside...
You used "_" to ignore the error variable, which I call "IDGAF" placeholder.
So, you willingly ignored the error and tell me that you don't have to check the error? You told the compiler that you don't care about the error explicitly (via "_"). That's on you then.
In my first comment I noted in the P.S. section:
You used "_" knowingly. Compiler/linter didn't add it there by itself.I mean, do you even read the language documents to understand how a language works?
rowanG077 | 12 hours ago
package main
import ( "errors" "fmt" )
func getMessage() (string, error) { return "DO NOT INSPECT", errors.New("something went wrong") }
func main() { msg, err := getMessage() fmt.Println(msg) fmt.Println(err) }
maleldil | 9 hours ago
This can't happen in Rust.
bayindirh | 14 hours ago
I have commented inside the code, but to recap here:
So, you need to both declare and use the err variable to be able to compile the code. So you can't forget. Your code will not compile.The only way to "forget" is to declare err as "_", which I call IDGAF placeholder, and this is a deliberate choice to ignore that variable. So you willingly and knowingly ignore the error variable.
Otherwise Go won't give you Go ahead.
Seriously, try to compile the example I have given. It's fresh, so hold with mittens.
vander_elst | 10 hours ago
``` a, err := f() b, err := g() if err != nil {} c:=a+b ```
The language will happily build and run, even though it should prevent to let you shoot in the foot.
bayindirh | 10 hours ago
In a little blunt form: This machine has no brain, use yours.
madeofpalk | 9 hours ago
Go errors if you try to assign a number to a string, so it's clear there is some intention for the machine to catch when your brain makes a silly mistake.
bayindirh | 9 hours ago
I can also think a couple of cases where I deliberately catch the error, but don't do anything on it explicitly, esp. if I'm talking with a buggy hardware. I'd still log the errors as INFOs or WARNs though. I have seen too many "task failed successfully" errors in my life.
vander_elst | 8 hours ago
I hope it's clear that what was proposed above is just a toy problem to show the issue. In complex parts of the code, unfortunately, these errors sometimes happen. That's a fact.
A programming language should actually try to protect you from some logic errors. This is why have we programming languages in the first place. Otherwise why bother with garbage collection, types etc? Should we just use assembly then for maximal flexibilty and putting all the burden on the programmer?
bborud | 10 hours ago
But as someone else pointed out, unhandled errors are very rare in practice in Go because every Go project tends to use static code analysis tools that catches this. When people talking about things blowing up during runtime I can’t help but think that they can’t be serious Go users.
On every build I generally run vet, lint (revive), staticcheck, gosec and test. And that’s the “light” build that doesn’t run leak analysis, fuzzing, race testing, benchmark regression and integration tests in addition. The light build is still faster than the Rust compiler. I haven’t compared the heavier build.
When people pretend the absence of features is a huge problem, I tend to think that these are people who either aren’t regular Go users or perhaps they are more interested in debating languages than writing code.
Let’s not pretend this is something it isn’t.
madeofpalk | 9 hours ago
The fact that external tools exist that "every Go project tends to use" to fill common a gap in the type system indicates to me that maybe the language itself could be improved.
bborud | 7 hours ago
Look at exceptions in Java, for instance. In theory the mechanism is there to guarantee that errors will be handled. In practice there are multiple schools of thought — several of which purposefully circumvent this mechanism by throwing runtime exceptions because they think checked exceptions just leads to a lot of unnecessary work.
It has been too long since I used Java to remember all the arguments for and against. But I do remember trying multiple approaches and realizing that only using checked exceptions turned out to be a bit too inflexible. That there were legitimate reasons to use runtime exceptions.
Go’s type system is probably “good enough”. Sure, I’d love to have option types and results. And it would have been nice to move some of the things in vet, lint, staticcheck into the compiler. But in _practice_ separating them is actually beneficial. Because it allows you to trade build speed for security. For instance when you are making a tiny change and you just want to make sure it still builds. That’s a _practical_ tradeoff you could not have made if the compiler were always strict.
One thing I wish Go would have adopted is Javadoc-like comments with explicit markup to document parameters, return values and possibly panics. Those were _more_ useful in practice than exceptions in Java because you could force people to be more deliberate when designing “contracts”. When I did Java we used to have the build fail if classes and methods did not document all params, return values, exceptions etc.
I also taught people to write unit tests while _only_ looking at the Javadoc for what they were testing and notating at the code. Even after years of doing this, I’d regularly find that implementations didn’t behave as documented when writing tests. This made people care about interfaces/seams/APIs/contracts and spend less time obsessing over having the compiler save them from sloppiness. It put the developer in the driving seat rather than have them run behind the compiler and just nudging things into a state where it compiles. It helped people think more about why something failed from a design point of view rather than put all their faith in the tooling.
I think that’s the biggest mistake of Go: it doesn’t care about documentation and to the extent it does, it introduces really, really stupid, pointless formal rules that does nothing to help developers.
tomhp | 22 hours ago
wannabe44 | 19 hours ago
The problem is painstakingly and manually having to build the stack trace, and then unwrapping it few layers above if you ever need to check what error it was.
bayindirh | 14 hours ago
I have written a fresh example at https://files.bayindirh.io/misc/error_example.go. Give it a Go. ;)
OTOH, gopls is a great LSP, though, and it warns you about this immediately.
comex | 13 hours ago
bayindirh | 13 hours ago
Considering some expensive Go calls do the same caching underneath, this is the preferred design pattern, I assume.
wannabe44 | 7 hours ago
yoghurtwere | 16 hours ago
I am skeptical about Go's error handling, but there are cases where it is desirable to return both a result and also an error, like returning what can be returned while warning users about any errors. That can be modeled in Rust and other languages as well, though it is the default for Go.
KronisLV | 7 hours ago
Unpopular take, but I personally also believe that unchecked exceptions in Java and other languages like that are an issue - because it makes too easy to miss where things could go wrong, instead of making you visibly handle the potential known failure points, or indicate to callers what you're not handling.
akerl_ | 18 hours ago
kubb | 15 hours ago
akerl_ | 10 hours ago
kubb | 10 hours ago
That’s a total statement, not leaving any room for criticism.
TACIXAT | 16 hours ago
Also, if error wrapping hurts you so much (I don't use it), just implement a project-specific error that works how you want. This could be something that is JSON serializable, that captures a line number at each return site, etc. It will take like 10 minutes to get your project's errors working exactly how you want.
My projects usually do -
That essentially logs a stack trace with line numbers up the whole error chain, each return adding the outer context. I only ever use errors.Is for os.ErrNotExist.wannabe44 | 7 hours ago
I think your error solution is good. But still it's a poor imitation of Exceptions wherein you have to capture the stack trace manually, possibly incuring a perf hit (haven't measured), and write handling code in every layer. Made worse by the fact that people return desperate errors for even precondition violations which should be panics.
hoppp | 10 hours ago
2.That error handling is one of the best features. It makes me explicitly acknowledge the errors instead of letting them just happen. No error goes unnoticed!
Each if err != nil is an explicit reminder to check, do I need to clean up? Do I need to log this error to a file?
"And no more oh an error happened I wonder where"
With LLMs verbosity is not an excuse anymore. Just generate it and focus on other things then
gempir | a day ago
Someone | a day ago
Even if you prefer manually checking for errors after every call that might fail, I fail to see how one can love go’s verbosity. Compare go’s
with something like (hypothetical) where the compiler, seeing that bar returns an Either<int,err> can enforce the presence of the ||| clause or, alternatively, require later code to check for errors if the ||| clause isn’t present. I think that’s both more robust (prevents one from forgetting to check for errors) and shorter (allowing for showing a lot more code on a screen or page)mayama | 17 hours ago
bborud | 10 hours ago
While the first occupies more lines I suspect I’d spend less time spotting it in the code. The physical shape of the expression is something my brain is used to seeing after 40 years of programming. Even when code grows more complex.
The second is OK when it stands alone, but I am not so certain it would be as easy to spot in more complex surroundings. Even on a single line, there is something a bit ugly about the code. Even before we get to the desperate triple-pipe. That kind of reminds me of the desperate attempts at repairing JS after they realized its equality semantics were shot. Just heap more characters on it.
The thing is: it is actually very hard to judge how ergonomic a language is by just looking at it. You have to use it and you have to use it enough to realize where the paint points really are. I’ve read through a lot of the responses in this discussion and I’ll be honest: I think a lot of people who criticize other languages (be they Rust, C, Go or whatever) aren’t very fluent in them. It takes a couple of years to develop fluency.
eptcyka | a day ago
sethammons | 20 hours ago
foo := thinger()
_ = foo # no longer unused
spockz | a day ago
My only wish is that go could become less verbose. There are several frontends to go that are more compact, compile down into golang, and then let you enjoy all the benefits.
BobbyJo | a day ago
asdf88990 | 19 hours ago
thiht | 12 hours ago
asdf88990 | 11 hours ago
thiht | 8 hours ago
BobbyJo | 5 hours ago
aaa_aaa | 13 hours ago
vander_elst | 10 hours ago
b7e7d855b448 | 9 hours ago
ksec | a day ago
hxtk | a day ago
ksec | 8 hours ago
jzelinskie | a day ago
[0]: https://github.com/authzed/spicedb/tree/main/tools/analyzers
verdverm | a day ago
An example from one of my recent projects: https://github.com/verdverm/gmd/blob/main/Makefile (give agents simple "tool calls" instead of needing to divine the correct args/flags every time, essentially invocable agents.md content)
One of the interesting things to call out from this is using build tags for testing { unit, coverage, recorded, real api }, with the buffet allowing the agent to iterate faster and more targeted. I tend to run the linting and coverage in a new session, have a report generated, and then another fresh session to start dealing with gaps.
Another super cool testing tool in the Go internal source is `testscript`. Roger Peppe extracted a number of those internal utilities here https://github.com/rogpeppe/go-internal/tree/master/testscri...
mchav | a day ago
fractorial | a day ago
bijowo1676 | 16 hours ago
The early loop looked like this:
Later it looked like this:eonwe | 16 hours ago