Why functions that may panic don't have an explicit extension to their name?


#1

When I first discovered rust I immediately liked the concept of the language.
(The people developing rust are doing a good job.)
There is only a little point that seams inconsistent within the language, at least for me.
When You learn the language You will run into the case where the program does panic.
I don’t critisize that it panics, it makes sense when it does.
This possibility for for the program to panic should be more obvious to the programmer.
Like if all functions that may panic have a “_panic” extension to their name.

If You are a C++ programmer like me and try to get knowing rust these panics make You feel that You just left the minefield of c++ bugs (heap corruption, access violation …) and went into the panic minefield.
This would also help when doing peer reviews, to help to focus the attention on points
that may cause problems. This could also bring the habit into code to extend dangerous functions like that.

So my Question are:
Why functions that may panic don’t have an explicit extension to their name, so that they can be identified easily ?
Is there a complete list of functions in std that may cause a panic ?


#2

Not directly answering the question as I’m semi new too, but so far the libraries that I’ve used that could fail gracefully tend to return Result<T, E>, which leaves the responsibility of handling the failure to the caller (e.g. I failed to parse this file), whereas code that panic either comes from in-development libraries or things that should fail (e.g. I couldn’t load the graphics library to create a window).


#3

With that said, it seems like rustdoc could potentially be enhanced to flag functions that panic.


#4

There’s a few reasons I can think of, in no particular order:

  • Code should only panic as a last resort, e.g. out-of-bounds indexing. In all other cases, failure should be handled gracefully by returning Result<T, E> (or Option<T> if that’s more appropriate).
  • If a function can panic, its documentation is supposed to explain all of the situations in which a panic can occur. Of course, things like out-of-memory can also cause your code to crash, but they’re a bit more difficult to handle gracefully. :stuck_out_tongue:
  • By default panicking triggers an unwind, which means that drop() is called on all types that implement Drop, and the rest is just thrown away because it doesn’t need any cleanup. As long as you don’t use panic = abort, you can panic, but you don’t have to panic. If you do use panic = abort, and you panic…you should. Or shouldn’t. In any case, you don’t want to panic while you’re panicking, because dreadful things happen. But don’t panic.

AFAIK there isn’t. But again, panics are supposed to be a known quantity, so if you get a panic that wasn’t documented, that’s a bug.


#5

The other problem is in composing things, not unlike noexcept in C++.

  • I called a method you gave me, so now I need a way to specify that I don’t panic, but will appear to panic if you panic
  • Can a trait method panic? If I .clone() something, do I need to mark myself as panicking?
  • How do I prove to the compiler (and myself) that I’m not dividing by zero, and thus it’s ok that I didn’t mark myself as panicking even though I divide stuff?

And anyway, unsafe lets you do things far more scary than panic! without it being visible to your callers…


#6
  1. Almost every function can theoretically panic.
    • Any function that indexes an array/vector/slice can panic.
  • Division can panic. In debug mode even + can panic.
  • If you implement panicing Clone/Hash/Eq/Ord, etc. then every generic function that allows your type can panic, even on == or <.
  • If you create a malformed string (which is only possible with unsafe code, BTW), every string function can panic.
  • Anything that takes a closure can panic, because the closure can panic.
  1. There’s no need to bring special attention in code review, since panics are merely a symptom of bugs.
    Look for bugs! Check math for division by zero. Check logic around indexing or use iterators instead. Test unsafe code extra hard.
  2. Panics are safe in general. They don’t corrupt the heap. There’s no access violation. Rust tries to panic before anything bad happens, and gracefully clean up as much as possible.

So to sum it up, it’s not done, because everything would be marked as panicing, you couldn’t do much about it, and it wouldn’t matter much anyway :wink:


#7

How would you mark operators?

let one = 1;
let a:u32 = 4_294_967_295 + one;

Panic ideally should only be triggering in code because of logic errors or when unrecoverable. It is a mechanism for terminating safely and not a runtime error handling system.

Rust code seems to go down the other route of making non-panicking versions prefixed with “try_”. You should be reading the docs of individual functions before using them rather than try to gauge what panics using a list.
https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.borrow

The big one you read in tutorials/examples; unwrap().


#8

Right. And any kind of memory allocation can panic as well. This is obvious for heap allocations (although I’m not sure if the unwinder itself can recover from memory allocation failure), but it also applies to stack allocations. The stack is a finite resource, and it is possible to exhaust it even without recursion.


#9

Shameless self-promotion: I made a library where it doesn’t, in case you want it.


#10

Ok, You have convinced me, that is a bad idea.


#11

Interesting. Why does Vec returned moved arguments in case of allocation failures? Is it so that it possible to recover the original program state if necessary?

(And I would have started with Box and maybe Rc.)


#12

Yes, exactly, all the collections do so.

I actually added Box in the repo earlier today but forgot to publish, derp. It’s up now and should be on docs.rs soon.


#13

Just for the sake of it: you can call a function that may panic and avoid the panic yourself, with catch_unwind. I had to use that because a crate I’m using panics when connection’s lost instead of returning something sensible.


#14

I mostly agree with you, except that I think it’s usually possible to ‘catch’ panics depending on how they’re configured: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html

Of course that doesn’t mean you can always fix a panic using catch_unwind, it depends on why the code panicked in the first place.


#15

heap corruption, use after free, etc. may be security critical bugs, where as Rust is guaranteed to safely shut down execution of the program.

panic! is an absolute last resort. If you use a crate and need to “catch” a panic at some point, that crate is poorly designed, as it should have returned a Result in the first place.

Actually this depends. If there is the possibility of an index being out of bounds, you can use Vec::get, which returns an Option. For example think about a slideshow application and the user navigates past the last slide: You want to gracefully prevent the navigation instead of crashing the whole program.

Summarising I want to say: Whether to panic! or Result depends on the situation, but if in doubt (even just slightly), use a Result.


#16

No, if your Rust program has any of those bugs (either due to incorrect use of unsafe or safety bugs in the language itself or unsafe library code), heap corruption or user-after-free pose exactly the same risks as in C/C++ programs.

The point of Rust is to stop those things from happening, but if they do happen, all bets are still off.


#17

I was talking about non-unsafe Rust, and I was not taking compiler bugs into consideration.

If you take compiler bugs into consideration, really anything can happen in any language.


#18

I had same thoughts about Rust’s pinicking politics, as topicstarter. IMHO, there are too many situations where panic can occure implicitly.

For example, before i have started read the Rust’s tutorial, i have been supposing, that indexing of arrays and vectors either returns some optional type (Option/Result) or, as more fast way, allows indexing only by some “Indexer” type (that internally is unsigned integer, but already bounds-checked). It was unpleasant surprise for me, that Rust just stupidly throws panic when out-of-bounds. Now i know that there is .get method returing Option, but natural form of indexing goes against Rust’s statement of writing reliable programs.

So, i have a questions:
1). Does Rust always-always performs bounds checking when indexing arrays and vectors? If i have already bounds-checked index, does Rust still check it again? If Yes, Is there faster way, like proposed “Indexer”?

2). Is there some compilator’s flag or something other, that will warn me about possibility of implicit panicking (unconditional bounds checking, using of “expect” or “unwrap” with Option/Result and other similar situations)?


#19

No. First of all, it’s only [] that checks; you can use things like get_unchecked, which is unsafe. Second, if Rust determines that the bounds check cannot fail at compile time, it doesn’t insert the runtime check.

Nope; it would warn in many places.


#20

Pretty much every function would have that extension then…