Hacker News new | past | comments | ask | show | jobs | submit login

> Even better, if there’s an error, there is no non-error return value. You can’t accidentally use the zero-valued return half of a tuple (as you can in golang) because it simply isn’t there.

That is better! But it's not as better as I think you think it is. The conventions are adequate, here.

> Is the important part of error handling having some copy-pasted stanza repeated everywhere? Or is it enforcing that errors are always handled and semantically-undefined return values are never accidentally passed along in the event of an error?

Neither, really: it's about having the error code path visually equivalent to the non-error code path.

> No, it simply is not. `?` early-aborts the function and returns the result straight away if it’s an error, and unwraps the interior value if not. There is no plausible way for someone to mispredict this behavior, and if there was, it would be no different from golang, since the two constructs are semantically virtually identical. One is simply shorter than the other.

I don't want early abort. Don't know how else to say it. If I have 5 operations, each of which can fail, I want them to be 5 visually distinct stanzas in my source, and I want to be able to manipulate the errors from each independently.

> Ease of understandability is almost hands-down the most important metric given the ratio of frequency to code being read versus written. And to be completely blunt, it is flatly ridiculous that wrapping every line in nearly-identical error handling code somehow doesn’t impair comprehension. The argument is the same for abstractions like `map`, `select`, `reduce` et al. Intent and behavior of code can be understood at a glance when you remove the minutia of looping, bounds-checking, and indexing

I'm sorry, but I just don't agree. You call looping, bounds-checking, index, etc. minutia, but I don't see it that way.




> I don't want early abort. Don't know how else to say it. If I have 5 operations, each of which can fail, I want them to be 5 visually distinct stanzas in my source, and I want to be able to manipulate the errors from each independently.

Sure you do, in 95% of cases. That's why the whole

    if res, err := fn(...); err != nil {
        return nil, err
    }
stanza exists in the first place. And if you don't want it (in Rust), Result being a first-class value means you don't have to early-abort. You just don't type `?` and you operate on the Result directly.

I have to say I'm pretty certain at this point that you haven't actually used Rust, because your points here just... aren't how things work in the language. `?` desugars to the golang error stanza almost verbatim, and if you don't want that you have plenty of other options for how to specifically handle your errors.

> I'm sorry, but I just don't agree. You call looping, bounds-checking, index, etc. minutia, but I don't see it that way.

Wild. It's incomprehensible to me that

    foo
        .filter(|v| v % 2 == 0 )
        .map(|a| a + 1);
is somehow less clear to anyone, or that anyone could think there's less room for unintended bugs than

    res := make([]int, len(items))
    for _, v := range items {
        if (v % 2 == 0) {
            res = append(res, v + 1)
        }
    }
when literally 95% of that code is boilerplate. It's immediately clear in the first example that we're incrementing every even number. In the second example, you have to visually parse significantly more code to get the gist, you have to remember to allocate an slice of the right capacity to avoid multiple reallocations for large slices, and you even end up with a slice that's too large for the data it contains in the end.

There's no argument for the go loop being "better" than the Rust equivalent that doesn't also argue that the C version with the additional hassle of bounds-checking and manual incrementing is better still.


> if res, err := fn(...); err != nil { > return nil, err > }

It's bad practice, though unfortunately extremely common, to return unannotated errors like this. I can't think of the last time I've used this stanza. The proper form is, at a minimum,

    if err != nil {
        return nil, fmt.Errorf("executing request: %w", err)
    }
Or, if there's additional, caller-actionable information about the error you want to provide,

    if err != nil {
        return nil, OtherError{Inner: err, Extra: xyz, ...}
    }
and so on. The point is you have in that stanza the space to program with the error, same as any other value in the function. The... semantic equivalence? which the idiom reinforces is actually extremely good! Error handling isn't any less important than happy path code, and, IMO, language features like `?` suggest that it is.

> It's incomprehensible to me that...

It is not immediately clear that the first example is incrementing every even number. To get there, we have to parse the method names, recall and parse the special syntax rules for those methods, and, if we're being diligent, reflect on the ownership requirements and allocation effects w.r.t. their parameters, to make sure we're not doing anything with unintended side effects.

We're doing basically the same work in the second example, minus the ownership stuff. We're using more characters to do it, but that's not a priori worse. Parsing `res = append(res, v+1)` does not take more time than `map(|a| a + 1)`. Using curly brackets and newlines to demarcate transformation steps instead of monads is not more prone to bugs. It's the same stuff, expressed differently, and, IMO, more coherently: code written in the imperative style is generally easier to understand than functional. (I hope that isn't controversial.)

> There's no argument for the go loop being "better" than the Rust equivalent that doesn't also argue that the C version with the additional hassle of bounds-checking and manual incrementing is better still.

Reducto ad absurdum.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: