Hacker News new | ask | show | jobs
by simion314 1504 days ago
>To be concrete, let's talk about an example of a panic. Say you want to access the 3rd element of a vector. There are two cases:

Reality is not that simple, if you worked in this industry you would know. For example I was building a web scraper years ago and the WebView would crash since is C/C++ , instead of doing it's job and show a web page or a broken web page it crashed my entire program,. The solution was to split my program in a parent program and a child program so this bug does not bring my entire thing down, and I can crash the issue and record the bad url that crashes and try again or just skip it.

I would hate to use Rust libraries that would crash my entire program if they for some reason are bugged. In my experience I found bugs in many popular libraries. So in Rust if I import a say library to resize an img and say the img is corrupted and library is shit it will crash my entire program? I would prefer a higher language where I can try=crash the image resize function and if shit goes wrong I can show the user a relevant message , or fallback to some other resizing method.

3 comments

> I would prefer a higher language where I can try=crash

What you're describing is the `catch_unwind` mechanism that Rust does have. Because panics are implemented with unwinding (by default), you can catch them. But it's not the normal error handling mechanism; it's the "oh god an assert just failed, or we just OOMed or something, who knows, most bets are off" mechanism. If you have a main loop that's sufficiently isolated from individual tasks, such that you think you can do something useful with the fact that one of your tasks just vanished in a puff of smoke, then catching a panic coming out of a task might be a reasonable thing to do. That often makes sense in server code, where your main loop might want to keep trucking, or at least gracefully shut down other connections. But for most library code, the right thing to do is to allow most panics to propagate and crash the caller.

So for example in JS a correct regex can throw exception on some input , so in the places where this can happen we can use a try catch . What do you do in Rust , do you check the return result and on top of that do you try to catch a panic just in case the regex library is bugged ? so you have to implement everywhere 2 error handling methods to be 100% safe? If yes seems more ugly to have to implement 2 error catching ways.

YEs it happen to me many times to hit bugs when working in real world, bugs in image libraries, bugs in regex libraries, bugs in pdf libraries, bugs in html/xml parsers so from my experience working with c/c++ and higher level languages I prefer the higher level languages, less bugs, almost no complete crashes and better error reports from the exceptions. I never had the tiem to try Rust but I am not tempted so far.

> What do you do in Rust , do you check the return result and on top of that do you try to catch a panic just in case the regex library is bugged ?

Nope, we just check the return result because libraries usually don't crash and have well-defined error cases. Having a decent type system helps catching all the possible outcomes. In a few years coding Rust, i never had a single crash due to a library panic, only from explicit unwraps i applied in my own codebase.

Panics are not intended for errors, but for unrecoverable failures. For example, in rust std a failing memory allocation will crash your whole program, which is in most cases what you want to do. For the remaining cases, there are other non-fallible methods.

For example: String::reserve vs String::try_reserve or HashMap::insert vs HashMap::try_insert.

There is no 100% safe anywhere. Does your JS code handle out of memory errors with try-catch? No, it will abort as if nothing happened at all.

Sure, there are bugs in every code but unexpectedly panicking is considered a bug in a library so in my not too extensive experience with rust libs, these are not the norm at all. So simply writing code where you yourself don’t panic should give you quite a high chance of not hitting this case ever.

>Sure, there are bugs in every code but unexpectedly panicking is considered a bug

Yes so would you like say Firefox to just crash when one of it's many dependencies crashes?

You are suggesting but I am not sure if I understand correctly that only memory errors cause panics? So what if the library reads a file and unexpectedly it shit happens with the file, it will crash the program because the developer maybe forgot to return a special error code in this case,

All the filesystem APIs return Results rather than panicking, since like you said it's expected for those to fail sometimes and for the program to handle those failures. It's possible for a library to convert those Results into panics by calling .unwrap() on them, but that would usually be considered a bad design (ok for tests and tiny programs though). So I think you have an important point here, which is that if your application is calling into a library that you worry might have some bad design decisions in it, you do have to worry about it bringing down your process. And maybe it could make sense in some rare cases to try to isolate that library with catch_unwind. But I think most Rust programmers would prefer to just fix the dependency. The fact that you can visibly spot a lot of these conversions in the code is helpful for auditing.

I'm not super up to speed on JS, but I might draw an analogy to Python. Handling a result in Rust is similar to catching an exception of a known type in Python, a very common thing to do. On the other hand, catch_unwind is (loosely) similar to writing a bare except clause that catches every conceivable exception. You can do that, and sometimes it's correct to do that, but in most cases it's a bad idea. You don't want to accidentally suppress errors that indicate a bug in your program.

Thanks, from my experience with desktop apps in managed language I alwas added a global catch for crashes that were not caught or can't be caught, there I was writing the details in a log file. Then I had a menu entry for submitting a bug report, a popup would open and the user had the option to include the log file with the exception information and details like operating system, runtime version etc. The only thing that was bringing down this app in the higher level language(it was an Adobe AIR app in Action Script 3) was the freaking Web View , because was a wrapper over WebKit and that was C++.

This days doing backend dev I am forced to move stuff in a different process but most stuff I use I prefer to use binaries then libraries , for example for resizing an image instead of using the built in image library that crashes sometimes and brings the script down I install image magic on the server and write a script for resizing an image then call that script and check it's output , sometimes I had to use the timeout linux program to kill the program if it gets stuck on some input file.

If I were to create that image resize library in Rust I would attempt to catch everything , including panics and return it as a error result(so only system crashes would be uncaught)

IO errors are generally handled by returning a `Result` type, that contains the details of the problem on error. You wouldn't use `panic` for IO errors. `panic`s are meant for dealing with broken invariants/assertion failures because of a bug in the program.
You need to run it in a separate process. Rust does not have good enough fault isolation features to safely assume a buggy image processor won’t break your app.

* Entering an infinite loop can bring down everything. A separate thread might not, but since Rust provides no way to kill a thread without it cooperating, there is no way to stop a stuck thread without bringing down the whole process.

* Stack overflow is an instant abort, not a panic.

* Double panic, where panicking calls a destructor that itself panics, is an instant abort.

Question, if you are a library/program author why would you intentionally use a panic and not cleanup and return an error? Maybe I misunderstood and in fact good developers never trigger panics unless there is no way to avoid it, like if they could not prevent it with more checks or is it impossible to cleanup because they already fucked up, wrote garbage in the process memory and safest thing is to kill the process.
I think there are a few cases where Rust likes to panic, but different people probably have different opinions here:

- Extremely common operations with dedicated syntax, where introducing error handling would be burdensome. Things like array indexing or arithmetic overflow. In these cases, you usually want an alternative, fallible way to do the same operation.

- Cases where most callers will probably convert the error into a panic anyway. One example of this might be .split_at() on slices, which is bounds-checked just like an array access. Most callers would probably just .unwrap() the out-of-bounds case, and callers who don't want it to panic can easily check before the call, so it's more ergonomic to panic.

- Cases where the only plausible reason for failure is a bug in the caller. For example, the .borrow() and .borrow_mut() methods on RefCell will panic if a write overlaps with another read or write. The caller is almost always expected to statically guarantee that that doesn't happen, usually by making all borrows short-lived. (And here again there are fallible alternatives available.)

An interesting example of something that doesn't panic, but which probably should, is taking a mutex. The standard mutex in Rust includes a "poisoning" mechanism, which almost every caller just .unwrap()s. I think the majority opinion these days is that poisoning should just be removed, but given that it's around I think most people wish it just panicked instead of returning a Result.

> is it impossible to cleanup because they already fucked up, wrote garbage in the process memory and safest thing is to kill the process

That’s essentially it, yes. My code should never actually panic. If it does, it means the state of the process has become deeply diseased, and attempting to “clean up” is likely to just make things worse. Of course, if it’s safe Rust, then it still won’t write past the end of a buffer or anything disastrous like that, but buggy code is still buggy code and there’s lots of stuff Rust won’t stop you from doing.

One of the more extreme things I’ve done in production Rust code was add a “watchdog thread”. It has a channel that takes unit and receives on a timeout, and the thread doing the actual work is expected to send it a message once a minute. If it doesn’t receive a message within a minute, it hard aborts the process. The default setup is run under a service manager like systemd to make sure it gets restarted, and that failures are actually logged somewhere.

This is meant to solve the problem that safe Rust is a Turing complete language, so is subject to the halting problem. The type checker can prove that you won’t read past the end of a buffer, but it cannot prove that your code will ever actually finish running. Which means, if you have a project like a web scraper that needs high uptime, you need to prevent it from getting stuck somehow.

I agree, so Am I wrong or the issue seems to be community culture thing, where some developers panic too eagerly? Say I make a library for resizing images and I have one public function resizeImage(options) , a good developer would think that maybe my code code has a bug and some function from standard library would panic, I should ensure I catch this and my public function never panics (even if there is no memory,disk or whatever Ias an author I should try not to intentionally panic" where a bad Rust developer thinks like " this will never panic unless I made a bug, if I amde a bug I am happy to panic and crush some sucker program so I get the bug report and fix the bug".

There are always bugs(logic bugs where Rust can't protect you) so why not have a clean interface?

IO errors like running out of disk space would be handled by returning a `Result` type, not by panicking. Often, Rust code/libraries panic on out-of-memory errors because recovering from that isn't a priority for most application code. But if you are writing lower-level or high-reliability code and you do want to handle out-of-memory errors, the Rust standard library (and many third-party libraries) offer alternative fallible memory allocation APIs that return `Result` instead of panicking on out-of-memory.
I disagree.

A good developer will crash the program, as soon as possible, if and only if it has a bug. If you want to write a program that never crashes, then you need to write a program with no bugs.

The reason you don’t want buggy code to limp along after it detects a bug, is that crashing isn’t the worst possible thing.

The worst possible thing is getting stuck in an infinite loop or a deadlock.

> I would hate to use Rust libraries that would crash my entire program if they for some reason are bugged.

You would, but for other programs with other requirements it would actually be beneficial. There's no single right answer, and you should pick the library that follows your particular requirements for that particular program.