Hacker News new | ask | show | jobs
by wannabe44 2 days ago
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.
7 comments

> 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.

> which makes handling errors mandatory

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.

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.

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.

You don't need a linter. Practically Go code will not compile without declaring and using the "err" returning from a function call.

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.

The problem is that this stops working once you have multiple assignments to err in the same function.
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.

What if the method only returns an error, like file.Close()?
> with a convention that you must checktror a non-nil error before using the result.

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.

> 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.

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.

Let’s not pretend this is something it isn’t.

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.

It's not hard to ignore an error in Rust, IME?

Especially as someone who's read a lot of code written by newcomers to Rust.

Unwrap is an explicit action. Checking an error is usually just forgotten. No unwrap can enter your code without the dev being accutely aware of it.
Here's an example file: https://files.bayindirh.io/misc/error_example.go

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.

If you have

``` 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.

> if the code goes boom, it's on the developer

This is the same argument C (and Zig!) people have for manual memory management. You can avoid memory problems by being a good developer.

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.

You pasting this comment link on all the replies clearly shows you don't understand what actually went wrong in this Cloudflare outage. But Rust bad.
.unwrap().

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.

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 )
I think the GP deserves the snark. Uncritical praise of everything about a language isn’t intellectual curiosity.
Nobody is suggesting uncritical praise.
> I love everything about go

That’s a total statement, not leaving any room for criticism.

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
i.e. the blub paradox: https://wiki.c2.com/?BlubParadox
Steelman: the extra complexity of languages more powerful than Blub has not been found to be a good tradeoff. Blub is KISS and that's good.
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.

> difficult to correctly use concurrency primitives,

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.

Go is a great language for when you want to just accomplish some server-side business logic with minimal bullshit standing between you and it.
https://pkg.go.dev/slices#Contains ?

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

Your handwritten one has a major performance bug:

    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.

> 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.

Even the 70's, that is ignoring what was happening with CLU, Smalltalk, Mesa, Modula-2, PL.8, ML, ...
or contains() could be written by those who can write performant code, ..once
You're right. Just for information, slices.Contains() uses the index, not a copied value.

https://cs.opensource.google/go/go/+/refs/tags/go1.26.5:src/...

> 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.

You forgot to put /s after your post.