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

I wrote a comment here pushing back somewhat on the idea that Rust has much better error handling ergonomics (not correctness, where it clearly wins, but ergonomics) but thought better of it; debating Go vs. Rust is about the least productive thing we can do on HN.



I like what Chris Lattner said about language design in Lex Fridman podcast, that you know when you're doing something right because it "feels" right. Rust has a bunch of syntactic sugar which gets critised in many languages, but to me things like error returns with ? "feels" right. Love Go as well, and the error handling makes a lot of sense in concept but it just doesn't "feel" nice to look at or use.

I like there's a language that has such a strong vision, but dealing in absolutes has drawbacks, a little sytactic sugar would make error handling a lot nicer.


eh having used both, rust's approach isn't any better. when you need to decorate an error, which generally is a lot of the time, rust has the same ergonomics as golang. they optimized code for a generally rare situation.

I do appreciate rusts match syntax though.


For what it's worth I really like how go handles errors.


I’m with you in this one, Go’s error handling was intuitive to me. It was my first compiled and typed language, though. That might bias my experience.


> that you know when you're doing something right because it "feels" right

Absolutely false. When you write the really groundbreaking and important software "feels right" is absolutely not what you should be experiencing.

The correct mode you should be in is "holy fuck how did I get here, and can I come out in one piece"?

(A: no, you can't, but the new you will be a better you.)


If you don’t have a result type then you don’t have better error handling!


Zig disagrees. Monads are cool and all, but we can do better than Result for error handling.


Result is monadic but not necessarily a monad.

From what I know of zig’s error handling, I really don’t think it’s better than result types.

It is less work, especially when using error unions (though I think that makes it easier to miss changes to error sets as they’re implicit), but since errors are literally just u16 they’re also deeply lacking in richness and composability.

It’s definitely an improvement over C’s error style, and thus perfectly understandable given zig’s goal, but I very much disagree that it’s “better than Result”.

It’s also extremely magical in that it needs special language-level support throughout in ways Result is not, but obviously that’s more of a matter of taste.


> It’s also extremely magical in that it needs special language-level support throughout in ways Result is not, but obviously that’s more of a matter of taste.

That's a peculiar observation, it's like saying Rust's borrow checker is magical because it needs language-level support. I mean, that's the point.

In any case re: zig error or rust's Result, I don't think either of us are discussing features and pros/cons, but just describing what we like the best. Not very objective an argument.

> but since errors are literally just u16 they’re also deeply lacking in richness and composability

Zig errors are composable: https://ziglang.org/documentation/master/#Merging-Error-Sets and later sections.


> That's a peculiar observation, it's like saying Rust's borrow checker is magical because it needs language-level support. I mean, that's the point.

You can’t do borrow checking without language support (I think, at least not without a significantly more expressive type system) whereas Result is pretty much just a type, the features it uses are mostly non-exclusive, and those which are, are very much intended not to remain so (e.g. the `Try` trait for the `?` operator).

> Zig errors are composable: https://ziglang.org/documentation/master/#Merging-Error-Sets and later sections.

Not really? That’s just merging error sets, but you can’t say that you’ve got an error which originally comes from an other error, except by creating its dual and documenting it as such. Essentially your choices are full transparency or full type erasure.


> Not really? That’s just merging error sets, but you can’t say that you’ve got an error which originally comes from an other error, except by creating its dual and documenting it as such. Essentially your choices are full transparency or full type erasure.

What you just described are error traces. They're generated by Zig auomatically for you, thanks also to the fact that errors are a special construct in the language.

Relevant langref passage:

https://ziglang.org/documentation/master/#Error-Return-Trace...


Don't you need sum types/sufficiently powerful enums for Result though? Which, in a way, makes it a language feature.


You’re confusing the chicken for the egg.

Sum types are a powerful language feature, but they’re also a general language feature, they don’t exist for the sole purpose of creating a Result type (and indeed a number of languages have had the former and lacked the latter).


I still don't get what's wrong with having custom syntax or language facilities to support a feature. Why is that a problem?


Do you mean that in contrast to affine types, that are here for borrow checking?


No, I mean that in contrast to the other side of the discussion (zig’s errors).

And affine types are not here for borrow checking, it’s closer to the opposite. Affine types are a formalisation of RAII, borrow checking is a way to make references memory-safe without runtime overhead which mostly avoids unnecessary (and sometimes impossible requiring allocations & refcounting) copies of affine types.


AFAIR, Zig has an open issue on how to pass additional context with errors, such as e.g. row & col where a syntax error happened.


To clarify I thin parent means syntax error I'm the sense of "I'm parsing a (code) file at runtime and there's a syntax error", not a language syntax error.

I mean there's nothing stopping you in zig from creating a rich "error" struct and returning a union of the struct with whatever you would have done otherwise. You only lose the error return trace.


"I mean there's nothing stopping you in zig from creating a rich "error" struct"

Thus Rust's std::result::Result<T, E> where the E in Err(E) is anything that implements the std::result::Error trait.

Rust got this right. It satisfies most use cases with the least possible noise and accommodates the weird ones easily, and does it in a std:: codified manner where no one has to wonder whether or not there is anything stopping them.


Have you seen what a "rich error struct union" looks like in zig? It's very easy and very legible code. I'd further bet that there are no cases when you're doing this that you want an error return trace. And the downstream handling code is very easy.

If there is a problem with how it's done in zig, it's a community one: maybe the pattern needs a megaphone beside it, like examples in the main docs or tuts in the various zig tut sites that have cropped up


"Have you seen what a "rich error struct union" looks like in zig?"

No, and I'm not arguing that zig hasn't done well. I do argue that Go has not; you're only non-weird choice is to fetter code with miles of "if err ==/!=" gymnastics.

Traces are frequently very useful; precise traces have saved me a lot of pain in life. Lack of traces precludes nothing for me, but I have enjoyed the benefit of them enough times to acknowledge their great value.


Traces are fantastic for unexpected errors. You don't need them, for example, when validating user input, which is the type of system where you want richer errors.


(E is not required to implement that trait, it just often does)




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: