Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I lament what Rust could have been had its designers not jumped on the anti-exception bandwagon. Rust's error handling is bad and makes me prefer C++


Seriously? Rust's error handling is great, and frankly I'm glad that exceptions have gone out of favour. I tried them but they never really delivered their promise. At their best they do is give you nice stack traces. At their worst they make error handling stupidly verbose, they erase the context you need to properly handle errors, and they make it much more difficult to even know which errors can occur!

Rust's solution is the best I've seen so far. Go's multiple return values are pretty good too but I think Rusts's is better.


I very strongly believe exceptions are a lot closer to optimal than Result is. Exceptions remove the need for inline error checking code and make it possible to implement types with value semantics. You can't have reasonable value types that own resources if you need explicit error checking.

The need for explicit error checking makes OOM handling in Rust awkward at best. I've ranted about this side effect before.

Also, in Rust since we can panic, we need to worry about exception anyway! Rust has the worst of both kinds of error handling.

Exceptions are anti verbosity: you're supposed to let them propagate, not catch and rethrow them. They don't erase context: they preserve it, since an exception object is constructed as close as possible to the error site that caused it.

I realize that I have a minority view, but I'm utterly convinced that I'm right and that I'm living in a world gone mad. I've written a ton of code over the years. At least hear me out and try to understand my perspective.


Personally I love Rust's error handling, but I do think that I understand your perspective.

At least part of your objections would seem to be addressed by the combination of the ? operator to make it trivial to do the equivalent of re-throwing inline, and the error-chain crate, which can make exception-like stack traces/etc trivial to implement.

https://brson.github.io/error-chain/error_chain/index.html


I think I know what this trend is, more generally. It has to do with how explicit our code is. We've been through an era where you have some very powerful and compact indirection constructs(event callbacks, polymorphic objects, dynamic types, exceptions) in common parlance and the trend has turned against these lately. Their utility in many instances is mostly to enable technical debt, by worrying about the edge case later, and this has scared a generation of coders who have seen it go astray too often and create code that is hard to usefully refactor. And while exceptions aren't at the center of that trend, they're often implicated as a contributor.

With the new trend, you pay up front and go verbose, put more logic at the call site instead of indirecting it away. Go is at the leading edge of that: lots of LOC for boilerplate is OK in Go-land.

I'm more pragmatic with my error handing methods: I mostly care about whether I can eliminate a class of errors altogether, and secondarily what debugging implications are presented. I don't have a strong opinion on verbosity although I have followed the trend in that respect towards more call site logic.


This trend represents the unlearning of very hard earned lessons. Personally, I can't wait until people rediscover that programming can be more fun and productive without boilerplate. A language being explicit, by itself, is not a feature. Being explicit instead of implicit is only worthwhile if you get clarity in exchange, and boilerplate is clarity-reducing because it's so regular and so obscures program logic.


In Rust, in the common case, the boiler plate you're talking about here is literally a single sigil. (Previously, it was `try!(...)`.)

This is of course to say nothing about the advantages of having something that signals "this operation can return an error," (at the call site) but you seem to dismiss that out-of-hand.


I do dismiss this advantage out of hand: almost everything can fail, because most things allocate memory. It's only because Rust treats memory specially (incorrectly --- memory is just another resource) that it doesn't appear that more functions can fail. It's much more valuable to flip the sense of the annotation and mark the few functions that cannot fail in any way.

Allowing most code to throw just reflects reality and allows you to stop obscuring your program logic with mechanical error handling plumbing.

All you do with "try!" is annoy readers by constantly reminding them that things can go wrong when the default assumption should be that things can go wrong.


(I just want to tell you somewhere that you are not alone, and that I appreciate the time you are taking to have this conversation. The arguments you are making are the exact same things I am often found saying, and in my experience it takes hours of time alone in front of a blackboard with someone to really get them to understand concept like "everything can fail" and "memory is no different than disk space". The one thing I haven't seen you argue with yet is that functions which are adamant they can't fail today often find themselves in a position where they can fail tomorrow, such as by adding authentication or storage; this makes "explicit" error handling a limiting contract that either requires you to proactively return error codes from everything even when they only currently return Success, never extend the functionality of existing systems to avoid accumulating an error condition, be prepared to break that contract in the future, or fall back really really hard on panic.)


> It's only because Rust treats memory specially (incorrectly --- memory is just another resource)

Which is a perfectly reasonable trade off to make. Of course, it's not always the right trade off, so we're looking to improve our story there.

If you can't acknowledge that there are real trade offs at play here, then I don't really see how it's possible to meaningfully understand your position (or have a non-frustrating conversation).


Go is more verbose because it lacks a type system that allows generic programming, that seems like a very different kind of verbosity, than explicit error handling IMO.

Correct me if I'm wrong, but I don't believe Go's type system enforces you check and handle errors either, so it's not really enforcing verbosity where it counts.


> Correct me if I'm wrong, but I don't believe Go's type system enforces you check and handle errors either

Go's type system doesn't, but the compiler will in a good number of cases. For example, this will produce a compile time error:

    value, err := Foo()
    fmt.Println(value)
It produces an error because `err` was declared and unused.

The following defeats this check though:

    value, _ := Foo()
    // Bar returns one value: an error
    _ = Bar()
    Bar()
The above list probably isn't exhaustive.


I think people would be more inclined to try to understand your perspective if you hadn't called Rust's design "jumping on the bandwagon", which, to me, implies a thoughtless act of conformity rather than a deliberate trade-off towards explicitness. To me, verbosity is bad, but implicitness is worse. I like the trade-off that ? (the question mark operator) has stuck for Rust.

I agree that OOM handling in Rust should be improved.

I don't agree with the way you talk about panics. They aren't just exceptions by another name because they're not intended to be used for handling expected errors (like exceptions are). Instead, they terminate the program. That's like attacking Java's System.exit() for not being just another exception. Many other environments share this distinction between fatal and recoverable errors. It can sometimes be a difficult choice to choose what to use, but having panics doesn't mean that all code must somehow try to handle them.


Panics are recoverable: initially at task boundaries, and these days at catch points. They are literally exceptions and unwind the same way. The designers intended for programs as a whole to keep running after a task panics. Code running in such a context needs to avoid leaking and corrupting resources on unwind --- i.e., be exception safe. On the rust development list, people call this property literally "exception safety".

I really do think that Rust error design avoided exceptions without properly considering the advantages of the exception model and the inevitably of turning panics into a full exception mechanism.


> Panics are recoverable: initially at task boundaries, and these days at catch points. They are literally exceptions and unwind the same way.

No. They provide roughly the same functionality as exceptions and are implemented the same way.

Here's the thing; exceptions implies using unwind recovery for error handling. This is not the case in Rust. The panic recovery API isn't designed to be used like this. No API relies on panics being recoverable. Panic recovery is supposed to be used for two very specific use cases:

- Stopping an application/service from crashing at a higher level (eg at an event loop boundary)

- Preventing unwinds from crossing into FFI.

Now, you could use Rust panics to build an exception system. It wouldn't be great, but you could. Sure. But a lot of the tradeoffs of a feature need to be considered in the context of how it's going to be used. Nobody's going to implement a (serious) exception library using Rust panics. Even if they did, it wouldn't work well with the rest of the ecosystem.

Panics are _not_ a "full exception mechanism". The tradeoffs are not the same as that of C++ exceptions (which are pervasively used). There are tradeoffs, mind you, but different ones.

> Code running in such a context needs to avoid leaking and corrupting resources on unwind --- i.e., be exception safe. On the rust development list, people call this property literally "exception safety".

Exception safety is a common issue between Rust and C++ but you rarely have to think about it in Rust (because all libraries are written assuming that panics may or may not abort, and very few applications catch panics and have to think about it for a very small area near the boundary), whereas it's more common in C++. Nobody writes libraries trying to avoid leaks on panics because catching panics is rare (and leaks are considered safe in Rust, though it usually requires contrived code to cause a leak in Rust).

Overall, it is not a problem. It's something you need to think of in extremely niche cases. Equating C++ exceptions and Rust panics is oversimplifying things.


I don't agree that panics are exactly equivalent to exceptions in languages like Java or Python because the language doesn't support using them as a general purpose error handling technique by providing `try/catch/finally` or by documenting what exceptions may be thrown with `throws`. I agree that the implementation of unwinding is similar in both cases, but I'd argue that that's not sufficient in practice to be able to apply knowledge about the usage of exceptions in, say, Java to the usage of panics in Rust.

I'm aware of catch-panic and the unwinding related to panics. Nevertheless, the vast majority of panic usage I've seen is related to the semantic that a panic signals an error that is fatal to the context of the panic. (This is a more nuanced view of panics that didn't exist in my previous post.)

Yes, you can use catch-panic on a server that is meant to stay up even if one of its threads panics. Or you can replace unwinding with aborting to save on code size. But idiomatic Rust code doesn't use panics simply to signal to a caller that some sort of typical error occurred like a file not existing.

Edit: I realize that my post may come across as arguing over semantics. I really don't care whether they're called exceptions or panics or even if they're mechanically the same thing. What I'm trying to talk about is whether they cause code to become harder to understand because of implicit error cases that are invisible in the source.

2nd edit: I appreciate your several good points in your replies to my posts.


> Panics are recoverable

You can't rely on this. Panics could just as easily abort your program. (By changing a flag on the compiler.)


If you write a general purpose library, you have to make conservative assumptions and work either way.

By the way: I am depressed as hell that the Rust people, knowing full well what went wrong in the C++ ecosystem, repeated the mistake of having an -fno-exceptions and thereby fragmentating the language.


In C++, exceptions are a first class error handling mechanism. This is not the case in Rust, so it can't be the same mistake.

Rust (and C++) are systems programming languages. Users must retain the ability to opt out of the cost of exceptions. The problem with C++ is that exceptions are a first class error handling mechanism.

> If you write a general purpose library, you have to make conservative assumptions and work either way.

If panics abort and they were our only error handling mechanism, then there is no other way to do error handling.

> All of this because some people don't like exceptions

On the one hand, you want people to understand your position. But on the other hand, you come off as implying the Rust designers (which includes the entire community) are a bunch of incompetent boobs that irrationally dismissed exceptions just because they "didn't like them." Do you see how these things conflict with each other? I'd suggest reconsidering your approach when engaging in discussion on this topic with others. As it stands now, you're extremely difficult to talk to.


I agree that it must be possible to use the language in a very low overhead, literal way. That's not most use, I think. Most applications can tolerate exceptions, so stdlib should have used exceptions to report errors. The people who can't tolerate exceptions are the same ones who want precise machine control and who probably don't want stdlib either.

If exceptions were the only way to report errors in stdlib, stdlib wouldn't have had to panic on errors --- or, rather, users would have come to expect these panics instead of treating them as a fatal, anomalous condition.

The trade off seems wrong here --- you should be able to support stdlib and exceptions for clarity or !stdlib and !exceptions for full control, but I don't see a big case for stdlib and ! exceptions, but the way Rust is designed, everyone pays for the stdlib and !exceptions model. (And they have to care about exceptions anyway, but can't rely on them.)

I think Rust's error handling is very poor design. I regret that this opinion coming across makes it difficult to have a conversation.


If your only error handling mechanism is exceptions and you disable exceptions because you can't bare the cost, then what are you left with?

> The people who can't tolerate exceptions are the same ones who want precise machine control and who probably don't want stdlib either.

I don't agree. Just because I don't want to pay for exceptions doesn't mean I don't want, say, convenient platform abstractions over file systems or I/O.

> I regret that this opinion

The opinion that you don't like Rust's error handling isn't the problem. It's all of the insinuations you're making about the people who worked on it. You paint a picture of carelessness and irrationality, but that couldn't be further from the truth.

The other problem is that exceptions vs. error values---aside from the performance or implementation implications---is a debate unto itself without a clear answer, but you pretend as if it's a solved problem and that your view couldn't possibly be wrong. On the other hand, I'm trying to sit here and say that there are trade offs, but you don't want to acknowledge them.


What is reporting the errors? In a standalone environment, in which you're left with the core language, anything that reports errors is something you can define, and you can define that component to use error codes, just as we would in C. It feels odd to want exact control over the error handling abstraction but want to use Rust's convenient IO abstraction. Performance either matters or it doesn't.

> debate unto itself without a clear answer, but you pretend as if it's a solved problem and that your view couldn't possibly be wrong

This debate was settled: we started out with error codes. We saw a generation of languages with exceptions --- Java, C++, CL with its condition system, Python, etc. arise in reaction to the problems with error codes.

The current movement away from exceptions, which I think started with Go, feels like backsliding, especially because most of the justifications for error code primacy that I see either ignore the actual (instead of mythologized) costs of exceptions or claim that exceptional code is a hardship inconsistent with my experience.

Now, it's possible that we should think of exceptions as just a failed experiment, but that view isn't consistent with the extreme utility I've seen in exception systems. Exceptions are so useful that people build them out of longjmp!

Anyway, it's frustrating that because Rust tried to solve simultaneously for ergonomics, exception freedom, and a rich standard library, it ended up in a position of having to abort on OOM. (I realize that there are more options these days.)

Still, exceptions in stdlib with an option for an exception free standalone system feels like the more appropriate trade-off.

I understand that there are trade-offs everywhere, but this observation doesn't mean that I have to excuse what I see as very bad trade-offs.


> The current movement away from exceptions, which I think started with Go, feels like backsliding, especially because most of the justifications for error code primacy that I see either ignore the actual (instead of mythologized) costs of exceptions or claim that exceptional code is a hardship inconsistent with my experience.

I don't think it comes from Go; rather it comes from the ML family (which Rust is arguably a member of). ML has had exceptions and error codes for decades, and ultimately that experience has come down on the side of error codes, because with good sum types and higher-order functions they are safer and more effective than exceptions. An analogy: early fighter planes were aerodynamically unstable, as it was hard to make them maneuverable any other way. Later fighter planes were stable as this was safer and easier to control. Modern fighter planes are aerodynamically unstable, as modern control systems can control such planes effectively and the original advantages remain.


> It feels odd to want exact control over the error handling abstraction but want to use Rust's convenient IO abstraction. Performance either matters or it doesn't.

This doesn't make any sense. What are the performance costs of doing I/O in Rust using `std::io`? If there are none, why would I want to give it up? AFAIK, the only reason to give up `std::io` is if your platform isn't supported by `std`.

> it's possible that we should think of exceptions as just a failed experiment

Who said that? Why does one way have to be right? There are trade offs! I'm sure you can find plenty of articles on the Internet that discuss exceptions vs. values. There are plenty of reasonable arguments on both sides.

> This debate was settled

OK, that's enough. I won't waste any more of my time with someone who is so certain of themselves.


You seem to be using the fact that there are trade-offs as a justification for the specific trade offs you've made and using tone policing as a substitute for defending these trade-offs


If you can't acknowledge the presence of trade offs, then I don't see how I could justify specific trade offs.

Just because I want to engage in a productive conversation doesn't mean my entire argument boils down to tone policing. It is OK to stop talking to someone because they are too frustrating to talk to.


Where did I disagree with the existence of trade-offs? What I find invalid is the idea that all trade-offs are equally good. The Rust scheme has certain advantages and certain disadvantages. I believe that the advantages don't matter much and that the disadvantages are worse than other people think. The advantages and disadvantages of the conventional C++ and Java model are better for a general purpose systems language.


> Where did I disagree with the existence of trade-offs?

When you say stuff like this:

> This debate was settled

and this

> I do dismiss this advantage out of hand

In general, most of your comments on this topic make every possible negative point about Rust's error handling without ever taking care to balance it with the positive points. If we can't even come to a mutual understanding that there are some trade offs involved in this decision, then it's really hard to move on to balancing the trade offs. Especially when you say things like this:

> All you do with "try!" is annoy readers

> had its designers not jumped on the anti-exception bandwagon

> without properly considering the advantages of the exception model

> All of this because some people don't like exceptions

This is a consistent dismissal of both the trade offs involved and of the people that actually worked on this stuff. Do you actually believe Rust is the way it is because we just hopped on a bandwagon? If so, that's extraordinary bad faith.


I'm criticizing Rust's error handling strategy. I shouldn't have to defend it at the same time. To be clear: everything is a trade-off. I don't think it's fair to claim that I don't think trade-offs exist merely because I haven't enumerated the scant good sides of the specific trade-off Rust made


> Anyway, it's frustrating that because Rust tried to solve simultaneously for ergonomics, exception freedom, and a rich standard library, it ended up in a position of having to abort on OOM. (I realize that there are more options these days.)

... if you realize that there are more options, please don't make this point, because the point doesn't make sense anymore. Aborting on OOM in Rust is a default, but you're not stuck to that system.


Sure, but then we're back to exceptions in one form or another, so now we have Result all over the place and have to deal with panics. Being able to panic on OOM won't go back in time and rewrite stdlib


> Being able to panic on OOM won't go back in time and rewrite stdlib

I don't see how that's relevant? If you have to deal with OOM you're probably not going to deal with it at a fine-grained level, you'll have one high-level panic catcher somewhere that handles this and all other panics.

Given that overcommit exists as well, this makes the cases where you want workable Result-on-OOM quite niche.

(And there is work -- lower priority work, but it exists -- for pluggable allocators which will let the stdlib eventually abstract over more of this)


Hundreds of millions of people use non-overcommit systems. That's a good thing, because overcommit is a mistake that encourages profligate use of system resources. I fear that abort-on-OOM will only reinforce the presumption of overcommit in the minds of developers. Even on overcommit systems, you can run out of address space or vsize.

I believe in treating memory like any other resource. You wouldn't abort by default when you run out of disk space, would you?


> You wouldn't abort by default when you run out of disk space, would you?

If most of the language and standard library required allocating disk space to function then I would indeed abort by default, because very few programs would be able to do anything useful in those conditions, so the most useful thing is to fail fast.

It's possible to design a language and standard library that can remain usable in out-of-memory conditions, but the costs would be severe, and not justified for the overwhelming majority of rust use cases, I think.


Overcommit wasn't my main point. My point was that the intersection of systems where:

- You are using the stdlib (so not designing an OS or other low level programming)

- OOM must be recovered from

- OOM-as-panic with recovery at a higher level in the application is not going to work and you need fine grained OOM recovery

is small. Overcommit makes it smaller, but ignoring that it is still small.


> You can't have reasonable value types that own resources if you need explicit error checking.

The proper place for the period in that sentence is before the "if"; owning resources is not a reasonable thing for a value type to do.


Tell me more about how std::string is unreasonable.


Are you talking about Rust's or C++'s? Name conflict :)

In Rust, std::string::String is not a "value type", and by that, I mean it's not Copy.


C++'s.


Then I won't comment, though I wonder if the parent is thinking of things like https://groups.google.com/a/chromium.org/forum/#!msg/chromiu...




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

Search: