RFC: Stabilize implementing panics as aborts

Stabilize the -Z no-landing-pads flag under the name -C panic=val
Implement a number of unstable features akin to custom allocators to swap out
implementations of panic just before a final product is generated.
Add a [profile.dev] option to Cargo to disable unwinding.

Rendered

:+1:

Okay, I have a bunch of thoughts which I won’t be posting inline, because of GitHub’s tendency to lose the comments in the long run.
Overall, I think this RFC is incorrectly conflating what current -Z no-landing-pads does with the panicking mechanism used. Namely, all -Z no-landing-pads does is turn off translation of cleanups. Presence or lack of these cleanups is completely unrelated concept to the mechanism used to signal unrecoverable error, IMO. A bunch of my further comments is strongly related to this problem:

There will currently be two supported strategies:

unwind - this is what the compiler implements by default today via the invoke LLVM instruction.
abort - this will implement that -Z no-landing-pads does today, which is to disable the invoke instruction and use call instead everywhere.

Right, so I have two points here: with this RFC you cannot disable codegen of cleanups and continue to use the unwinding panic – which is what -Z no-landing-pads does currently. Another point is that C library does expose the capability to register cleanups on abort calls, namely, by catching the SIGABRT signal on UNIXes and running all the necessary cleanups then.

Any crate DAG can only contain at most one instance of #![panic_runtime].

Similarly, previously any crate in the dependency tree could opt-out from cleanups (as an optimisation). One crate using -Z no-landing-pads does not break other crates which have them. This RFC, as written currently does not allow for this use case. You have to go all-in into one single option in all crates – even those that do not need it.
That being said, I do see the necessity to only have a single panicking mechanism in a final artefact, so the comment basically boils down to lack of distinction between panicking mechanism and cleanup mechanism or lack thereof.

Finally, as the one who landed the NoLandingPads pass for MIR, I would like to see some more discussion in the RFC on how the compiler is supposed to decide whether to generate traditional cleanups or not. I suppose it could be as simple as checking whether -C panic=unwind, but given my comments above, I find that lacklustre.

I have some minor reservations.
This only accomplishes half of the goal of creating no-unwind builds because it still uses a libstd with landing pads. Eventually we're going to get to a place where if you want this you are going to be using either a provided no-unwind std or building it yourself.
It's not clear to me how that future world will work with the new panic option in Cargo. In particular, I don't know how or if Cargo will be aware of which variant of std is in use.
If we're going to continue the pattern of #![needs_foo] traits to control the runtime composition, I want to change the #[allocator] attribute to #![provides_allocator] and #![panic_runtime] to #![provides_panic_runtime], to make the pairing of 'needs'/'provides' clear. It's easier to talk about as 'needs'/'provides' attributes. I realize these are unstable, but they will become defacto stable eventually. I might also want to generalize this pattern further to e.g. #[needs(allocator)] / #[provides(allocator)].

I like the overall thrust of this RFC, and am really excited to see abort semantics one step closer to reality!
My biggest concern is with the final drawback (which I talked with @alexcrichton about privately):

Applications may silently revert to the wrong panic runtime given the heuristics here. For example if an application relies on unwinding panics, if a dependency is pulled in with an explicit extern crate panic_abort, then the entire application will switch to aborting panics silently. This can be corrected, however, with an explicit extern crate panic_unwind on behalf of the application.

In particular, my feeling is that typically applications should be the one to choose the strategy, and all of their dependencies must be compatible with that strategy. (In the RFC, if any crate in the graph has abort semantics, the entire artifact does.) It's not hard to imagine pulling in some indirect dependency that silently changes from unwinding to abort, and I don't think that crate authors should be required to explicitly extern crate the desired panic strategy. If we're going to provide unwinding by default, I believe we should not silently change that default, at least for an application.

cc @wycats @nikomatsakis

cc @jamwt, because Dropbox has long been a proponent of abort-on-panic.

Yeah, I agree with having applications choose the strategy.  As long as we're allowed to ensure that all panics abort (as users of various libraries but owners of the application), we're good.

Oh wow, that was fast, thanks for the comments!
@nagisa
It's true that this RFC is diverging from what -Z no-landing-pads (an unstable option) strictly does today. It sounds like your main concern is preserving the ability to disable the generation of landing pads in a crate, but still continue to use unwinding as a panic strategy? I personally see that as a serious footgun that should be avoided at all costs. I at least couldn't come up with a use case for a crate where resources are intentionally leaked on panics. As a result, this RFC was written in a way to disallow this case entirely. Do you have a use case in mind though?
I'd be fine leaving the -Z no-landing-pads option lying around for just testing purposes, but in terms of a stable interface I'd want to have some more motivation before we stabilize the ability to intentionally compile a crate so it may leak resources on panics.

Another point is that it is possible to “register” cleanups on abort calls, namely, by catching the SIGABRT signal on UNIXes and running all the necessary cleanups then.

I'm not really sure how this is relevant, can you clarify? The whole point of the unwinding strategy on Unix today is that it's zero cost in the case where no exception is thrown, and if we had to register a handler every time we had a function call that... wouldn't be zero cost?
I will say, however, that if this is just an alternate strategy of handling panics, this RFC should account for that. We would implement another -C panic=sigabrt in the compiler, and that would codegen whatever was necessary to install handlers and such (and then we'd ship a panic_sigabrt crate with runtime support).

I would like to see some more discussion in the RFC on how the compiler is supposed to decide whether to generate traditional cleanups or not.

I suppose I didn't really discuss this as I'm not really sure what the discussion would be in the first place. My current interpretation of the state of MIR and such would be:

MIR continues to have the concept of an "invoke vs call" similar to LLVM, where call instructions can return in an abnormal fashion to another basic block which has a bunch of cleanups.
For -C panic=abort we'd run the NoLandingPads pass you've implemented.
For any -C panic=foo where foo is not "abort", we wouldn't run NoLandingPads and the MIR would looks the same regardless of panic strategy.
When lowering to LLVM, call instructions that have an exceptional return edge (to a cleanup block) would be translated appropriately depending on the value of -C panic. For example -C panic=unwind means that we'd use the LLVM invoke instruction.

Does that make sense? Did you have other concerns? I'd definitely want to make sure that any MIR plans align well with this RFC!

@brson

This only accomplishes half of the goal of creating no-unwind builds because it still uses a libstd with landing pads. Eventually we're going to get to a place where if you want this you are going to be using either a provided no-unwind std or building it yourself.

I think that when rustup.rs is ready and we're distributing/managing multiple compiled modes of the standard library we'd still basically want almost everything in this RFC. The first alternative mentions this explicitly by indicating that one major change could be disallowing mixed panic modes in a DAG entirely (instead of allowing a panic runtime to link with crates compiled any which way). Are you thinking, however, that other parts of this RFC would change in the face of such a distribution mechanism?
Also, the only drawback I know of with linking an unwinding libstd with an aborting application is that the object code in the standard library itself may have missed optimization opportunities based on the fact that functions will never unwind. Do you know of other drawbacks, however? Much of this RFC is foundationally built upon the idea that linking aborting applications to the libstd binaries we ship is good enough in basically all situations.

It's not clear to me how that future world will work with the new panic option in Cargo. In particular, I don't know how or if Cargo will be aware of which variant of std is in use.

One critical portion of this RFC is the validation that the compiler performs to ensure that any sort of weird situations like this can't cause problems at runtime. For example the possible scenarios I see here are:

If you have an unwinding std and panic = 'unwind' in Cargo, everything will link A-OK
If you have an unwinding std and panic = 'abort' in Cargo, everything will link A-OK
If you have an aborting std and panic = 'abort' in Cargo, everything will link A-OK
If you have an aborting std and panic = 'unwind' in Cargo, then a few things are possible:

In reality, we'd probably have extern crate panic_abort included in this build of libstd. That way you'd get an error immediately if you tried to compile anything with -C panic=unwind and link to this libstd.
Otherwise, if we didn't include this, the compiler will auto-choose aborting panics. This sounds pretty bad, and is essentially @aturon's concern, and is convincing me that we may want a more principled error in this case.

Basically, the compiler should always be able to provide a principled error message in these situations. Along those lines I'm not really sure what the negative interactions would be between Cargo and choosing the right libstd, but we should be able to relatively easy encourage the user to select an appropriate toolchain.
Did you have other concerns in mind, however?

If we're going to continue the pattern of #![needs_foo] traits to control the runtime composition, I want to change the #[allocator] attribute to #![provides_allocator] and #![panic_runtime] to #![provides_panic_runtime], to make the pairing of 'needs'/'provides' clear. It's easier to talk about as 'needs'/'provides' attributes. I realize these are unstable, but they will become defacto stable eventually. I might also want to generalize this pattern further to e.g. #[needs(allocator)] / #[provides(allocator)].

Yeah I'd be fine with basically anything here. I didn't put too much thought into it because they're unstable. I somewhat disagree that these will ever become defacto stable (in the panicking case). It's essentially impossible to write your own panic runtime without compiler support, so I don't think we'll see many of those any time soon.
Writing a custom allocator, however, that may indeed hasten the defacto stability!

@aturon

If we're going to provide unwinding by default, I believe we should not silently change that default, at least for an application.

And to clarify, this RFC does allow for the case of any crate to explicitly choose a panic strategy via extern crate panic_{abort,unwind}. Along those lines, the worry here is that projects may assume that the default unwind strategy is getting used without ever asserting that it's getting used. This RFC then will subvert expectations and silently switch to panic=abort if any crate in the DAG links to panic_abort and the panic_unwind crate is never mentioned.
This, coupled with @brson's concerns, I believe, have convinced me that this needs to be a first class compiler error. Specifically if a final artifact is being produced the compiler will assert that the panic runtime selected is compiled in the same panic mode as the final artifact itself.

@jamwt

As long as we're allowed to ensure that all panics abort (as users of various libraries but owners of the application), we're good.

You can indeed! It should be as simple as:
extern crate panic_abort;

And then you're done!

To clarify, does #![needs_panic_runtime] solely exist to make it trivial to detect cyclic dependencies?
I also agree that this pattern of paired needs_foo attributes is begging for a more general solution.

Regarding the need to ship two versions of std: Hypothetically, if LLVM extended to other platforms the Windows model of outlining cleanups into separate funclets, those could be placed into separate sections, and stripped out by the linker when not needed.

To clarify, does #![needs_panic_runtime] solely exist to make it trivial to detect cyclic dependencies?

That's part of it, but it also enables the logic in the compiler to auto-inject a runtime if one isn't found. This is essentially solving the case where so long as you never use the standard library there's no need for a panic runtime to ever get injected.

Are you thinking, however, that other parts of this RFC would change in the face of such a distribution mechanism?

Yes. As I mentioned, I don't see how the cargo option aligns with a world with custom stds. There will be some - yet undesigned - mechanism in the future to specify what combination of codegen/runtime features that users want the entire world to be compiled with. This new panic option in cargo is just one facet of that. I am concerned that it will be obsoleted by a more general mechanism in the future.
I am further concerned that baking the unwinding configuration into the crate's configuration that we are opening the door to crates being able to specify incompatible codegen/runtime options.
Things that need to be specified globally in the future:

CPU architecture
cogegen options like SSE, PIC, etc.
unwinding runtime strategy
unwinding codegen strategy
allocator
debuginfo

There are probably more I'm forgetting right now. These are all related and we should have a plan to solve this issue of custom codegen/runtime components generally, not piecewise.

Do you know of other drawbacks, however?

Bloat.

How does this interact with panic handlers? Will panic handlers be called before an abort?

@Amanieu No, there is no unwinding involved (hence 'abort').

I would like to propose an alternative design for this feature, just to throw the idea out there.
The main issue with -Z no-landing-pads is that it can easily result in undefined behavior for two reasons:

Unwinding from a nounwind function is undefined behavior.
A lot of unsafe code relies on destructors running to ensure safety.

However we could make -C panic=abort generate landing pads that simply call an abort function. This is the same mechanism that C++ uses to implement noexcept (calls std::terminate if an exception leaks out of a noexcept function).
Benefits:

No need to create multiple crates implementing panic and the complex machinery that goes with it.
Compile times should be pretty fast since landing pads are just a single call.
In many cases the landing pad can be optimized away if a function only calls nounwind functions.

Drawbacks:

libstd is still compiled with unwinding enabled, so panics inside libstd might execute a few destructors before the process is aborted. This shouldn't be a problem in practice though.
There is still some code size overhead due to the calls to abort in the landing pads, but it should be much smaller than the current landing pads.

@alexcrichton

It sounds like your main concern is preserving the ability to disable the generation of landing pads in a crate, but still continue to use unwinding as a panic strategy?

My main concern is conflation of panicking and cleanup strategies in this RFC. Every other major issue I have with the RFC stems from that.

I at least couldn't come up with a use case for a crate where resources are intentionally leaked on panics. As a result, this RFC was written in a way to disallow this case entirely. Do you have a use case in mind though?

Any crate at the branches of call tree could use the option of explicitly leaking resources as an optimisation. As stated in the RFC itself, the cleanups are not entirely free – they consume binary space in the final executable, they increase compilation times significantly. They also compete for CPU cache, decrease instruction locality and so on. I do not see e.g. numerics crate caring about a leaked vector or two if that helps to avoid generating a bunch of code for most of calls in computation-intensive functions.

I'm not really sure how this is relevant, can you clarify?

My only point here that conflating panicking and cleanup strategy doesn’t make much sense. I didn’t intend to claim it is a sensible cleanup strategy though.

For any -C panic=foo where foo is not "abort", we wouldn't run NoLandingPads and the MIR would looks the same regardless of panic strategy.

Right, so for some fictional process::exit(101) panicking strategy somebody implements for their own use-cases we would still generate unnecessary cleanups.

@Amanieu

Unwinding from a nounwind function is undefined behavior.

But we do not generate nounwind functions and it is crate author’s responsibility to catch panicks at FFI boundaries?

However we could make -C panic=abort generate landing pads

I do not see how it makes sense to generate landing pads at all if we panic by abort()ing in the first place. Are you suggesting we keep our only panicking strategy we have currently and focus on implementing multiple cleanup strategies instead?

Just a small nit: The PR summary comment uses a -C unwind=val flag instead of the proposed -C panic=strategy.

I have some concerns about the ability of an application to make an informed choice about the panic strategy.
Specifically, I am thinking that as a crate author I could perfectly use speculative execution and catch panics in case of issue. Today, from a user's point of view, this is completely transparent. Tomorrow, if the user decides to specify panic=abort, then my crate will halt her application (sometimes).
Worse, of course, is when a crate that previously was never generating/catching a panic suddenly starts doing so; as an application, it means that I need to audit each new version of the crates I pull to ensure their compatibility with panic=abort.
In essence:

I am seeing an implementation detail leak here => I now need to know whether a crate requires unwinding to work correctly or not
I am not seeing any control mechanism

To me, as a crate author, whether I am willing to commit to support panic=abort should be an explicit choice, in the same way that marking a function const is.

Rust already never guarantees destructors run though, so you can't make that kind of assumption. And when setting panic to abort, isn't "halt her application" what she'd want in the first place?
On Feb 27, 2016, 10:40 -0500<EMAIL_ADDRESS>wrote:

I have some concerns about the ability of an application to make an informed choice about the panic strategy.
Specifically, I am thinking that as a crate author I could perfectly use speculative execution and catch panics in case of issue. Today, from a user's point of view, this is completely transparent. Tomorrow, if the user decides to specifypanic=abort, then my crate will halt her application (sometimes).
Worse, of course, is when a crate that previously was never generating/catching a panic suddenly starts doing so; as an application, it means that I need to audit each new version of the crates I pull to ensure their compatibility withpanic=abort.
In essence:
I am seeing an implementation detail leak here =>I now need to know whether a crate requires unwinding to work correctly or not
I am not seeing any control mechanism
To me, as a crate author, whether I am willing to commit to supportpanic=abortshould be an explicit choice, in the same way that marking a functionconstis.
—
Reply to this email directly orview it on GitHub(https://github.com/rust-lang/rfcs/pull/1513#issuecomment-189669018).

Specifically, I am thinking that as a crate author I could perfectly use speculative execution and catch panics in case of issue.

@matthieu-m Of course you can do so if - and only if - you choose to ignore the warning in the std::panic::recover docs that tells you not to do this.
Perhaps "panic=abort mode" would finally provide a strong motivation against misusing recover as it shows directly what issues doing so can cause.

Now to my general comments on the RFC:

For -C panic=abort we'd run the NoLandingPads pass you've implemented.
For any -C panic=foo where foo is not "abort", we wouldn't run NoLandingPads and the MIR would looks the same regardless of panic strategy.

It's unclear to me which direction this design really chooses: While some parts of the RFC ("There will currently be two supported strategies") suggest that the set of available is clearly defined and very definitely not arbitrarily user-extensible, some of the discussion (e.g. the part I'm quoting here) appeared to be about user-defined panic strategies. This second version is also implied by the general design around (seemingly) arbitrary crates.
The major difference between this area and memory allocators is that the latter generally don't require any specific language support and hence can be implemented purely as libraries (crates). This approach doesn't really seem viable for panic strategies as these care about code generation (things like invoke vs call) and are therefore always tied to hardcoded paths in the compiler unless the design is powerful enough to allow each strategy crate to hook into these compiler paths on its own, e.g. by being loaded as a compiler plugin.
So to sum things up, I don't see how the current design benefits (at all) from utilizing a generic crate-based solution (versus e.g. just a compiler flag and some special piece of crate metadata that can be checked for compatibility at link-time), so I obviously don't think the downsides (conflicts etc) are worth it.

rustc would like to not use unwinding, but print a nice ICE message before aborting.

Specifically, I am thinking that as a crate author I could perfectly use speculative execution and catch panics in case of issue. Today, from a user's point of view, this is completely transparent. Tomorrow, if the user decides to specify panic=abort, then my crate will halt her application (sometimes).

One of the biggest concerns I (and others) had with stabilizing recover is that it would cause crate authors to believe that they should use it in this way.
I emphatically do not believe that crate authors should use recover in order to save themselves from dealing with intermediate Results. We have worked hard to make Result-based propagation ergonomic and to encourage the use of Results for handleable errors.
The idea behind panic! in Rust is to allow the propagation of fatal errors that can only be reasonably handled by destroying the entire isolation unit that they are a part of (threads, FFI boundaries or processes). By destroying an entire isolation unit, we encourage a programming model where integrity bugs (out-of-bounds access, integer overflow) do not propagate into the rest of the program.
On the other hand, handleable errors are expressed using Result, which can always be handled locally. By definition Result can't "do anything" to the caller, while exceptions can.
Is is a feature not a bug that this RFC discourages crate authors from using panics to represent handle-able errors. If Result is still not sufficiently ergonomic after ?, we should work to improve the ergonomics of handleable errors, not give up and let panic! become a handle-able error (as it is in Go).

I'm going to sound like a broken record here, but the confusion over whether recover would work with aborting is why I preferred to call it recover_from_unwinding so as to be maximally explicit at the capability that it provided (as well as a longer name further discouraging casual misuse).

I like that name 👍
On Feb 27, 2016, 21:30 -0500, Ben<EMAIL_ADDRESS>wrote:

I'm going to sound like a broken record here, but the confusion over whetherrecoverwould work with aborting is why I preferred to call itrecover_from_unwindingso as to be maximally explicit at the capability that it provided (as well as a longer name further discouraging casual misuse).
—
Reply to this email directly orview it on GitHub(https://github.com/rust-lang/rfcs/pull/1513#issuecomment-189766506).

I'm going to sound like a broken record here, but the confusion over whether recover would work with aborting is why I preferred to call it recover_from_unwinding so as to be maximally explicit at the capability that it provided (as well as a longer name further discouraging casual misuse).

If someone has a mental model that aborts could possibly be recovered from, they're gonna have a bad time. Many of those people would never encounter or see the recover API.
What else could we do to help avoid this mental model?

Making panic method unspecified will not break anything unless you're rusting wrong. Panics should be used as a drop-in replacement for Results.

What else could we do to help avoid this mental model?

Give recover a long an outlandish name, such as i_really_know_what_i_am_doing_so_just_let_me_recover_from_this_unrecoverable_exceptional_state_please_forgive_me. Furthermore, make it unsafe. I am well aware that many people are against "abusing" unsafe in this way, but I would argue that it is justified in this case, since it is easy to introduce logic errors by misusing recover.
But I'm afraid this is out of the scope of this RFC.

Panics should be used as a drop-in replacement for Results.

Did you miss a "not"?

Yeah, obviously :stuck_out_tongue:

If Result is still not sufficiently ergonomic after ?, we should work to improve the ergonomics of handleable errors, not give up and let panic! become a handle-able error (as it is in Go).

Go actually gives us an interesting case study. Namely, despite the fact that it can be difficult to write abstractions around Go's error handling via return values, use of panic/recover to do error handling instead is rarely abused in my experience. (There are some exceptions and other details that make this an imperfect analogy, but I'll avoid getting into the weeds and propose that it's at least worth considering as a success on their part.) Go's standard library and surrounding conventions has worked pretty effectively in the sense that the ecosystem uses return values to propagate errors instead of panics.

Go actually gives us an interesting case study. Namely, despite the fact that it can be difficult to write abstractions around Go's error handling via return values, use of panic/recover to do error handling instead is rarely abused in my experience. (There are some exceptions and other details that make this an imperfect analogy, but I'll avoid getting into the weeds and propose that it's at least worth considering as a success on their part.) Go's standard library and surrounding conventions has worked pretty effectively in the sense that the ecosystem uses return values to propagate errors instead of panics.

I don't think that's exactly right.
From an early post on Go's error handling story:

When you panic in Go, you’re freaking out, it’s not someone elses problem, it’s game over man.

panic("inconceivable")

panics are always fatal to your program. In panicing you never assume that your caller can solve the problem. Hence panic is only used in exceptional circumstances, ones where it is not possible for your code, or anyone integrating your code to continue.

In the original thinking, panics were inconceivable, always represented a bug, were "game over man", were always fatal to the program, and are to be used for cases where it's not possible for your code or anyone integrating your code to continue.
I think that's roughly the worldview we are trying for in Rust, but it's not exactly the truth in Go:
From [an earlier blog post]:

For a real-world example of panic and recover, see the json package from the Go standard library. It decodes JSON-encoded data with a set of recursive functions. When malformed JSON is encountered, the parser calls panic to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'unmarshal' methods of the decodeState type in decode.go).
The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.

And from the golang wiki:

By convention, no explicit panic() should be allowed to cross a package boundary.

Within a package, however, especially if there are deeply nested calls to non-exported functions, it can be useful (and improve readability) to use panic to indicate error conditions which should be translated into error for the calling function.

Using panics in this way has a number of problems for Rust:

Practically, it means that packages idiomatically require the runtime and unwinding machinery, because they use panic! to represent recoverable errors they plan to present to callers, rather than "game over man" conditions which could be handled fine with abort (which would be acceptable and more efficient in many scenarios).
It adds confusion about when to use panic! vs. Result. Today, the story is simple: if you plan for anyone to recover from the error, you use Result.
It relieves pressure on the language to improve the ergonomics of Result (as we did with ?) by allowing people to use panic! when the ergonomics cross certain tipping points. In my opinion, the pressure to improve Result has been very good for the project so far, but we're not quite done yet (it's still sufficiently annoying to build project-specific error enums, for example).

Let's see if we can take Result and ? all the way as the handle-able error story before accepting Go's "you can use panic inside a package as long as you convert it back at the boundary" story.

@aturon said:

In particular, my feeling is that typically applications should be the one to choose the strategy, and all of their dependencies must be compatible with that strategy.

I agree with almost the entire thrust of the original comment, but have a small quibble: I don't think that a crate should know which panic strategy is used, and I don't think crates should be writing code that depends on a particular strategy.
Since the decision of which strategy to use is made at the application level, allowing crates to choose a strategy that they require forks the ecosystem in practice. What that means for users is that sometimes they will want to use a crate that has nothing to do with panic and won't be able to because of the compatibility decision that the crate author made.
In exchange for that ecosystem fork we get roughly two things:

crates that explicitly asked for the abort strategy would be allowed to write code that used panic! and didn't ensure memory safety in the face of unwinding (it assumes that the abort takes care of it).
crates that explicitly asked for the panic strategy would be allowed to assume that unwinding happens, and catch panics at the library boundary and convert them into errors.

I don't think those things are a good idea on their face. Even if you thought they were a good idea, I don't think allowing them provides enough benefit to pay for results like:

I try to use the JSON crate and I can't because the author decided on the abort strategy for panics and I need the unwinding strategy for my FFI boundary.

From the perspective of a crate, panic! should be, in the parlance of the Go post I quoted earlier, "GAME OVER MAN". Someone else might create an isolation boundary (process, thread, FFI boundary) but the library that produced the panic shouldn't write code that knows which isolation strategy is in effect.

@wycats
If I write a kernel, I don't want unwinding to get in the way. I don't have to think about features that I am not going to use anyway. It does not matter how many crates I split my project into.
Similarly if I'm writing a tab-per-process browser - I might want to use unwinding for image decoding, but if it complicates my JIT implementation I might split that into a process of its own.
Actually, in a case like that, I might like to compile some crates (the image decoding crates) with panic enabled, and some other crates without it. If we don't want this to result in safety problems, we would need to link to the image code through a panic-free (unsafe? panic->abort?) API. That is probably desirable for other reasons.

Not done mulling over this RFC, but I think the "who specifies panicking strategy" question has a pretty basic answer in principle: libraries can choose "abort", "unwind", or "either"/"agnostic", and executables can choose "abort", or "unwind". The choices of each crate in the dependency DAG need to be compatible.

@wycats Your points are spot on and are exactly what I meant by "imperfect analogy." My point actually wasn't that we should adopt Go's approach (I very much agree that it is inappropriate for Rust), my point was to try and provide some comfort that the ecosystem won't start using panic/recover everywhere for error handling. :-)
(I've used the panic/recover idiom inside of packages with Go, but the specific reasons why one does that in Go don't transfer to Rust, since Rust has a bit more expressive power when it comes to error handling via return values.)

@Ericson2314 Keep in mind that, should such a system come into use, I'll consider anything but "strategy: agnostic" in a library to be a bad code smell, similar to when a library in this day and age doesn't take advantage of the myriad of free Continuous Integration services.

I don't see why any library should have to rely on any particular panic strategy. The only reason I can think of is code that becomes unsafe if unwinding occurs, such as take_mut. However this case is best handled by simply aborting in a destructor if an unwind occurs, which will work regardless of the panic strategy used by the application.

@ssokolow Absolutely! But considering it is possible to rely on one, I think it better to be able to come clean than try to mask the smell :).

@ssokolow Absolutely! But considering it is possible to rely on one, I think it better to be able to come clean than try to mask the smell :).

Whether or not we give people a direct way to express it will have a significant impact on how often it's used, and whether some parts of the ecosystem consider it a totally fine pattern.

@wycats fair. Well then disallow the field on library packages, and warn that it only applies to the executables in a package containing both.

On the general matter of "provides" and "supports", maybe Cargo features are ideomatic in the closed-world case, but they too are insufficient in the open-world case: editing the cargo file to add more features for each provides implementation doesn't scale.

@Amanieu

How does this interact with panic handlers? Will panic handlers be called before an abort?

I believe yes, we'd call panic handlers just before the abort.

However we could make -C panic=abort generate landing pads that simply call an abort function

You listed as rationale for this that unwinding from a nounwind function is undefined behavior and unsafe code relies on destructors running. @nagisa pointed out that we don't generate nounwind functions, so that shouldn't be too much of a concern in the compiler today. For the second point we don't actually guarantee that destructors run so that's unfortunately not part of the unsafe contract.
Some of the benefits you mentioned though sound like they would be good to achieve, so I wanted to address them in detail as well. The rationale behind multiple crates for a panic runtime is to "correctly" drop the runtime dependency of a stack unwinder in the sense that the symbols are never actually referenced in the generated object files. For platforms where it's difficult for these symbols to exist this provides a nice option for "working nicely" in that niche.
While compile times with small landing pads may be better, there's a few points to consider here:

Using invoke kicks us off FastISel in LLVM, which means we take a slower code generation path (from what I understand). In that sense the size of the landing pad may not matter too much when generating code.
When using small landing pads, we still have to provide symbols for personality functions, and these may not always be available.
The invoke instruction has been known to inhibit optimizations in LLVM in the past

Overall, I think that while small landing pads may indeed be faster than what we have today, we're likely to get more benefit from having a compilation mode that doesn't use them at all.

@nagisa

My main concern is conflation of panicking and cleanup strategies in this RFC. Every other major issue I have with the RFC stems from that.

Right, I guess what I was trying to get at is why this is an issue. I don't really see any "conflation" here per se but rather a preference to focus on one side of the issue (not the code generation aspect).
This RFC definitely would disallow optimizing a crate to have no landing pads while intentionally leaking, and I'll be sure to list that in the drawbacks

Right, so for some fictional process::exit(101) panicking strategy somebody implements for their own use-cases we would still generate unnecessary cleanups.

Can you be more specific to the unnecessary cleanups you're thinking of? The goal of this RFC would be to have zero cleanups for the panic=abort compilation mode

@matthieu-m
As pointed out by @main-- and @wycats, that's essentially not the intended use case for catching panics. All recoverable errors must be communicated through Result is the idiom of the standard library.

@main--

So to sum things up, I don't see how the current design benefits (at all) from utilizing a generic crate-based solution (versus e.g. just a compiler flag and some special piece of crate metadata that can be checked for compatibility at link-time), so I obviously don't think the downsides (conflicts etc) are worth it.

Yeah it's definitely true that it's impossible to have a user-defined panic strategy (because it needs codegen support). The crate strategy is intended to solve a few constraints:

Each strategy needs some form of runtime support, so it's nice to express this as a crate.
Leveraging crates means we get lots of compositionality benefits like using Cargo, etc.
Disjoint crates can have disjoint runtime dependencies, so we don't need to shim symbols or stub them out.

It may be possible to have some flags which control behavior at runtime, but I was hoping to select strategies at compile time to avoid runtime dependencies.

@arielb1

rustc would like to not use unwinding, but print a nice ICE message before aborting. Is that going to be supported?

Indeed! I suspect that a panic handler would probably suffice here. We've got to worry about rustdoc --test which runs the compiler concurrently internally, but that's easily fixable through a number of other means most likely.

@wycats

I don't think that a crate should know which panic strategy is used, and I don't think crates should be writing code that depends on a particular strategy.

I think this is a good point, and as also mentioned by @ssokolow I think it'll be an extreme antipattern to not be agnostic to how panics are implemented. We could try to make this explicitly in the RFC perhaps. For example library crates could be forbidden to link to panic runtimes. To me though that seems like an unnecessarily harsh restriction for something we just want to be idiomatically true. Worth considering though!

Abort-only libraries are roughly equivalent to C libraries with regards to their compositional properties. C libraries can be called from any Rust code and call any rust code regardless of its panicking policy, then why abort-only Rust libraries can't participate in "mostly unwinding" crate trees? Maybe some extra unsafe annotations are required to make it work?
Can abort-only Rust libraries at least mimic C libraries to workaround the restrictions?

Overall I think this RFC is proposing the right approach for panic handling. However there are a few points that are not addressed in the RFC:

How does this feature interact with no_std applications, such as a kernel? A kernel would want to be compiled without unwinding support, however it will probably ship it's own implementation of panic rather than using the panic crates.
Would it still be possible to compile libstd without unwinding, even if the one distributed by default will have support for unwinding (necessary to support both abort strategies).
You say that we don't guarantee that destructors run, but a lot of unsafe code, even in libstd itself relies on destructors being executed to provide a safe interface. Even the documentation reccomends using destructors to ensure safety in the face of unwinding. I think that rust should guarantee that destructors are executed if unwinding does occur. This would guarantee that any code higher up in the call stack will not see invalid state that could result in unsafety.

How does this feature interact with no_std applications, such as a kernel? A kernel would want to be compiled without unwinding support, however it will probably ship it's own implementation of panic rather than using the panic crates.

It... seems to me that this is addressed? A kernel would either use panic_abort, or would create its own crate along those lines.

Would it still be possible to compile libstd without unwinding, even if the one distributed by default will have support for unwinding (necessary to support both abort strategies).

Did you see https://github.com/rust-lang/rfcs/pull/1513/files#diff-1639dedf58714efcd09dfaba5f04535fR248 ?

You say that we don't guarantee that destructors run, but a lot of unsafe code, even in libstd itself relies on destructors being executed to provide a safe interface. Even the documentation reccomends using destructors to ensure safety in the face of unwinding. I think that rust should guarantee that destructors are executed if unwinding does occur. This would guarantee that any code higher up in the call stack will not see invalid state that could result in unsafety.

The ship has sailed long ago for guaranteeing that the destructor of an arbitrary values executes before it becomes inaccessible. Note however that we don't just skip destructors of random stack values: when people say that we don't guarantee it, they're referring to fairly specific classes of situations (see that linked RFC).

@alexcrichton

Can you be more specific to the unnecessary cleanups you're thinking of? The goal of this RFC would be to have zero cleanups for the panic=abort compilation mode

Namely, what I have in mind, that provided somebody implements their own panicking strategy crate which uses e.g. exit(101) instead of abort(), we’d generate cleanups for exit(101) but not abort() under the strategy you describe, while in reality one would like no cleanups for either.

@Amanieu

How does this feature interact with no_std applications, such as a kernel?

As @huonw mentioned this should be addressed, albeit implicitly. The compiler's logic for selecting a panic runtime is all gated on some crate in the DAG being tagged with #![needs_panic_runtime]. As a result, kernels likely don't have this, so they don't activate any logic in this RFC. Kernels would likely just compile with -C panic=abort and just call it a day.

Would it still be possible to compile libstd without unwinding

Although not explicitly addressed, as @huonw mentioned this will be supported and we hope to ship these binaries at some point. This is related to @brson's concerns above as well.

I think that rust should guarantee that destructors are executed if unwinding does occur.

I think this discussion is somewhat out of scope of this RFC, but yes to clarify we don't say "whelp no destructor may ever run". There are scenarios where can guarantee they run, I just don't believe they've been precisely specified.

@nagisa
I was actually thinking that "panic == abort" would indeed be implemented with exit(101) to mirror the program termination semantics that happen today (aborts trigger core dumps). Somewhat debatable, of course! I'm also under the impression, however, than any form of "panic == exit the program immediately" would generate no cleanups, as that almost defeats the purpose of it in the first place. This may be getting somewhat adrift though?

@alexcrichton
Right about "panic = abort", but someone might want "panic = print nice ICE message" or "panic = send backtrace to supervisor process" or "panic = just recycle the thread, I manage my heap with a GC" or whatever.

@arielb1 those seem all implementable with panic handlers? The -C option here is somewhat focused around the codegen part and then making sure the codegen and runtime match up. In other words a "print nice ICE message" definition of panic wouldn't require extra compiler support, so it could either be implemented as panic handler or as a panic runtime.

I would love it for the "panic strategy" to affect whether panic handlers are even supported. Panic handlers are basically a dynamic way to do what this does statically.

If someone has a mental model that aborts could possibly be recovered from, they're gonna have a bad time.

@wycats I'm more concerned that people using std::panicking::recover won't understand that Rust can legally translate panics into aborts, in which case your recover is meaningless (and other languages do offer ways of running cleanup logic on abort, e.g. signal in C, so it's not entirely unprecedented to expect an analogue).

@alexcrichton
Right. But if -C panic=abort implies that panics call abort() directly, this would break. Maybe call it -C panic=norecover.

@alexcrichton
Right. But if -C panic=abort implies that panics call abort() directly, this would break. Maybe call it -C panic=norecover.
I figure that the allocator-style mechanism is about panic handlers, right?

@arielb1
Hm yeah panic=abort may be a bit of a case of bad naming if it's intended to go to something like exit instead, but there may be a bit of a disconnect about panic handlers. Right now raising a panic looks like:

A call to panic!
The standard library boxes up the payload into Box<Any>
Invocation of panic handlers
Check for panicking during a panic handler
Calling the panic handler
Check for double panics
Invocation of the panic mechanism

All of this will remain the same in this RFC except for the last step. Essentially the rust_panic symbol (already a well defined symbol) will just be swapped out at compile time. The panic_unwind crate would do what libstd does today, and the panic_abort crate would also never return, but probably via exit(101) or something similar.
Along those lines, this RFC has very little to do with panic handlers other than saying that they'll still run on panics (before the actual panic implementation happens).
With that in mind, I don't fully understand what you mean by "this will break". If "this" is the ability to execute some code on a panic, it should be the same with this RFC as it is today. Also when you ask whether the allocator-like mechanism is for panic handlers, it's actually just for the runtime implementation of the panic itself. Does that make sense?

@alexcrichton a minor remark

The whole point of the unwinding strategy on Unix today is that it's zero cost in the case where no exception is thrown, [...]

The instructions for jumping to the unwinding strategy and the instructions of the unwinding strategy itself are part of the assembly generated. They do increase code size and might thrash the instruction cache. The cost is very low, but is not zero.
AFAIK there are three "zero-cost" alternatives, in the sense if you would implement it yourself you couldn't do it any better, depending on what exactly do you want to do:

the flexible zero-cost: "if panic, handle panic", which allows for different unwinding strategies for different parts of the program (e.g. calling user provided destructors),
the zero-cost: "if panic, abort" strategy, which keeps the cold code of the unwinding strategy to a minimum, which is important if you are extremely code-size limited and still want to handle errors, and
the no-cost: "if panic, continue" strategy, in which you just don't care about errors at all.

Probably only the flexible zero-cost approach should be the default, but I would argue that even the last one should be supported since it is widely used in embedded programming in C. It is not only that "doing no error handling is cheaper than doing some error handling", but in some systems you just don't care about errors at all (e.g. 3 embedded systems, two give the same answer, one does not, that one just gets rebooted).

I am interested in the needs/provides mechanism used here and in the allocator case. IIUC this pattern is basically a way of having a crate leave a dependency dangling which must be resolved later in the crate graph -- it also implies that this dependency must be fulfilled exactly once, since we have no way of "scoping" resolutions. It seems overall pretty similar to a lang item. I can definitely see how we might consider generalizing this pattern, or at least writing #[needs(alloc)] and #[needs(panic_runtime)] -- even if we don't allow some open-ended set of "requirements" like this.
Still, I wonder if we really need to have #[needs(panic_runtime)] declared explicitly -- it seems like something that the compiler could easily deduce itself just by checking if the crate panics (similarly, it seems like we could deduce whether an allocator is needed by checking whether certain fns are called or whatever).
As far as I can tell, the main purpose of these attributes is to ensure that the implementation of a panic runtime does not itself require a panic runtime (i.e., we do not recursively invoke panic!). However, it seems like the compiler could figure this out -- that is, if we can infer that you are using a panic runtime, we can also check that if your crate is tagged with #[provides(panic_runtime)], then neither it nor any of its dependencies require a panic runtime (and, similarly, allocators).
If we wanted to be more explicit, though, one might also imagine opting out of panics/gc, kind of like one opts out of libstd -- i.e., #[no(panic_runtime, alloc)]. Of course, this would be most appealing if we still used #[no_std], and I led the charge about renaming that particular option ;).
Anyway, this comment is a bit rambly, sorry, but I guess I'm interested in seeing how important this pattern is and whether it can be simplified or else generalized.

I'm trying to catch up on this thread (been on PTO last little while). My current thoughts:

I basically agree with everything @wycats has said:

libraries should not assume any particular panic semantics
libraries should use panic to signal "game over" scenarios and Result otherwise

To that end, I think it's very important to start stabilizing "panic == abort" mode, both because it's legitimately useful and because we want to send a strong signal that one should not assume any particular panic semantics.

However, whether or they are supposed to, it's clearly possible for a crate to implicitly depend on a particular panic strategy:

A crate can depend on abort semantics by writing unsafe code that is not exn safe
A crate can depend on unwind semantics by having crucial, externally visible cleanups in destructors

aborting will not execute those destructors
random example:

imagine writing into a file which will be deleted if an error occurs
use a destructor to delete the file if things go wrong

Both of these examples worry me, though for different reasons. The case of writing non-exn-safe code is worrisome because, frankly, exn safety can be quite tricky. If we wind up with a non-trivial set of crates that are explicitly targeting kernels and other cases where "panic==abort" is the norm, then those crates may not find it worth their trouble to be exception safe. This will in turn imply that they are not suitable for use in a more general context. This is precisely the kind of "ecosystem fork" that @wycats is talking about -- but I'm not sure if it is truly avoidable. It may be better in the long term to at least be able to label a crate as "abort only", since the alternative is that people may try to use combine crates that are "abort only" with an unwinding runtime, which will lead to subtle crashes and other flaws. Basically it may be that it would be better to let people label what they have done, and then use community pressure to argue for exn safety, since it maximizes the number of consumers that can take advantage of your crate. (I think this may have been what @arielb1 was saying in this comment? I'm not 100% sure.)
Now the second case is also a bit problematic. If you DO have cleanup that you want to occur, even in the face of catastrophe, it's not clear what you are supposed to do in an abort scenario.  Asking library authors to do anything beyond using destructors correctly seems unreasonable, and if there is any amount of unwinding, you might as well do it all.

Basically it may be that it would be better to let people label what they have done, and then use community pressure to argue for exn safety, since it maximizes the number of consumers that can take advantage of your crate.

That's sounds like a good idea. I was not sure whether we should allow for FFI linking of crates from both categories in the same process.

If you DO have cleanup that you want to occur, even in the face of catastrophe, it's not clear what you are supposed to do in an abort scenario.

Your process may exit without unwinding even if unwinding is enabled (power cuts, signals, taking exponential time to do something and getting the process killed by a supervisor, etc.).
The case that worries us more is libraries using panic::recover as control flow, to break out of deep recursion.

Actually, using panic::recover as control flow is not what worries me; I am more worried about the combination of abort and "best-effort" calls.
In the applications I've interacted with, there are usual essential tasks to be carried out and best-effort tasks that would be great to see carried out but which are not as essential; a simple example of such a best-effort task is logging (for investigation). If for some reason the log-consumer thread, in charge of writing log messages to a file or a socket panics (maybe the socket died on us?); I just want it rebooted transparently (or even can tolerate not having it).
Invoking those best-effort tasks within another thread or inside a panic::recover block is a simple way of ensuring that an indexing error in them will not accidentally take down the whole process, but it no longer holds with an immediate abort strategy.

@gnzlbg
Ah yes to clarify when I say "zero cost" I only mean zero cost at runtime, not compile time or binary size. There aren't actually any instructions which jump to unwinding related code (that's the zero cost part). The only cache effects from unwinding are that when you're executing code near the end of the function then the next function may just be farther away in memory because the unwinding code is at the end and pushes it back.
I'm not really sure I quite follow your comment though, but I can try! It's not the intention of this RFC to rework how unwinding panics are implemented or to allow multiple panic strategies in one DAG. It is intended to leave the "if panic, handle panic" case the default (so long as you mean implemented via unwinding). I don't think the "if panic, continue" can exist because the macro panic! must have the "return type" of !, or otherwise meaning that it can never return.
A panic handler can always be installed, however, to do something like reboot the system or abort the entire process unconditionally via a different means than the panic_abort crate.

@nikomatsakis
You bring up some excellent points! I'll try to address them all in detail

I am interested in the needs/provides mechanism used here and in the allocator case.

Yeah in terms of stabilization, I would basically never want to stabilize what we have today. To elaborate on what they're doing though, these attributes serve a few purposes:

For ergonomics, the compiler understands what the "default" is for a needs(foo) crate. For example the allocator is chosen automatically by the compiler on stable Rust depending on the output type.
For panicking in this RFC, the compiler also understands the connection between -C panic and needs(panic) (not sure how this could be abstracted)
I think the needs(foo) is much better as explicit. I'm not actually sure if there's a nice way to define a crate that requires a panic runtime without something that basically boils down to #![needs(panic_runtime)]. You mention that we could easily deduce this by checking if a crate panics, but we actually need to check if a crate implements the panic! macro, which is somewhat more difficult.
I wouldn't necessarily say the main purpose of these attributes is to prevent one panic runtime linking to another. That's certainly something that falls out, but to me the main goals of the attributes are:

They are not required in the 99% use case.
Explicit customization is always possible
Crates that don't have #![needs(foo)] never need to trigger the logic to find a #![provides(foo)] crate.

I may also be a bit rambly, but I think it is indeed worth thinking about these attributes. While they themselves will not be stable (and likely never will be) there are semantics about what they do which are indeed stable. For example linking to alloc_system will conflict with linking to any other allocator crate, which is part of the stable semantics (kinda).
For me, though, I feel that the stable portions of these attributes (in terms of semantics) are all relatively minor or "obviously ok" so I'm not too worried about stabilizing the functionality. Stabilizing the names or generalizing it to #[needs(foo)], however, I'd certainly be much more cautious about!

If we wind up with a non-trivial set of crates that are explicitly targeting kernels and other cases where "panic==abort" is the norm, then those crates may not find it worth their trouble to be exception safe.

I definitely agree that this is a worry (and what @wycats was getting at), but I don't really see a way around it. I think we should acknowledge it and aggressively message that this should not be the norm of course. We can always lead by example in the standard library and all rust-lang crates.
I also think that we need to provide authors recourse for messaging this intention. I would very much prefer to get a weird compiler error message saying I can't link together two crates because of panic runtimes then a mysterious leak or segfault in production. That's the main goal of the explicit extern crate panic_abort in this RFC. By signaling the intention we can ensure that runtime weirdness doesn't happen.

Now the second case is also a bit problematic. If you DO have cleanup that you want to occur, even in the face of catastrophe, it's not clear what you are supposed to do in an abort scenario.

I think @arielb1's comment about this is spot on. It's already impossible to reliably run destructors on process exit, so truly robust code simply cannot rely on such a fact.
Crates will certainly be "more ergonomic" with panicking, though. The best example I have of here is the tempdir crate. If you find yourself ctrl-c'ing a lot of compiler/cargo invocations, you'll probably see a bunch of /tmp/rustc-..... directories lying around because they're not cleaned up on process exit, only on Drop. This to me, though, is a bug in the tempdir crate.

@arielb1

I was not sure whether we should allow for FFI linking of crates from both categories in the same process.

Given that we already mark it as undefined behavior to panic across an FFI boundary, when combined with https://github.com/rust-lang/rfcs/pull/1510 (rdylibs) I believe it should be technically feasible to link two applications into the same process image which can alternate panic strategies. Each image won't share any code, basically.

@matthieu-m
I believe the use case you're describing is basically what unwinding was implemented for. It provides the ability to recover from failure via task isolation. The logging task would be entirely isolated from the rest of the application, so it's safe to tear it down and bring up a new one. In that way it's using panic::recover for its intended purpose rather than control flow.
I believe the worries about using recover for control flow are largely related to @wycats concerns above.

@alexcrichton, talking about "subecosystems", writes:

I definitely agree that this is a worry (and what @wycats was getting at), but I don't really see a way around it. I think we should acknowledge it and aggressively message that this should not be the norm of course. We can always lead by example in the standard library and all rust-lang crates.

I've been talking to @wycats a bit over IRC about this. I think I've decided this is an orthogonal issue from this RFC. Basically, we want the vast majority of crates to try and be agnostic about what kind of panic handling is in use. Eventually, we may want to add some kind of "sub-ecosystem" support to crates.io, so that creates can specify that they are only targeting kernel use cases or whatever, but for the time being we expect that the vast majority of people using crates.io are writing "general purpose" Rust code that is suitable for the full range of Rust applications; this implies that they should be panic agnostic. Maybe in the future we'll find we have "sub-ecosystems" with particular requirements/assumptions, and those sub-ecosystems have grown large enough to merit special recognition by cargo.
@arielb1 wrote (and @alexcrichton cited):

Your process may exit without unwinding even if unwinding is enabled (power cuts, signals, taking exponential time to do something and getting the process killed by a supervisor, etc.).

Yes, of course this is true, but I'm not really talking about ways to achieve 10 9's of reliability or what have you (after all: you can use processes, but what if the kernel dies? what if your cluster dies? what if your utility company goes out of business?). All error recovery is inherently a "best effort" sort of business.
In any case, I think I've decided everything here is also fine, at least for the moment -- for now, libraries should go on writing Drop code just as we do now for any cleanup, and they should assume that their consumers will only use panic=abort if they don't care about this cleanup (or have some other mechanism, like monitoring processes, for ensuring it).
It may eventually come to pass that eventually we want some other solution; some way for a library to register particular cleanups that should be run even if panic=abort. This would probably look like atexit handlers or maybe just building on top of the existing hooks for executing code on panic. Then you might have some abstractions that let you switch between dtors and "atexit" style registration, depending on the compilation mode. But we can cross that bridge when we come to it.

Yes, of course this is true, but I'm not really talking about ways to achieve 10 9's of reliability or what have you

Getting your process killed by a supervisor is not that uncommon (note: supervisors include impatient users with task managers). I mean, Firefox definitely gets force-killed WAY more often than it crashes.
I think that cleanup-list exception handling is a nice-to-have, and I can certainly see its uses - it handles cross-process resources excellently, and it can be used with signals - but a robust library should not do anything too horrible if it takes a SIGTERM or SIGKILL.
I think that crashing on random non-critical errors is the bigger issue, along with "intentional" uses of panics for control flow (the distinction here is rather thin - is a malformed configuration file a bug or just bad input).

@arielb1

I think that cleanup-list exception handling is a nice-to-have, and I can certainly see its uses - it handles cross-process resources excellently, and it can be used with signals - but a robust library should not do anything too horrible if it takes a SIGTERM or SIGKILL.

Yeah, fair. I guess this just reinforces the POV that Drop is good enough for now, and other cleanup mechanisms will be something we can consider later.

On the topic of thinking about #[no_std]:

A call to panic!

The standard library boxes up the payload into Box<Any>
Invocation of panic handlers
Check for panicking during a panic handler
Calling the panic handler
Check for double panics
Invocation of the panic mechanism

All of this will remain the same in this RFC except for the last step....

Remember panics! behind the facade have a different step 1 (at least). The mix of lang items and needs/provides here is a bit confusing. While I'm not sure that should hold up the RFC itself (nice to change those steps incrementally) would be nice to have the whole thing looked over before stabilization.

Ok, I've pushed an update to address @aturon's concern I outlined a solution to.
We also chatted about this in the core team meeting and one conclusion we came to was that we may want to simply outright disallow linking to panic runtimes. That would be a pretty effective tool in preventing libraries from forcing one panic strategy over another in Cargo because it would be impossible to libraries to do so.
When writing up this change into the RFC, however, I hit a snag. This details that libtest will explicitly link to panic_unwind because it fundamentally doesn't work with the panic=abort strategy. This would not be expressible, and as such it would be very easy to compile unworkable test harnesses. Cargo could have logic to cover all this, but it'd be easy to shoot yourself in the foot if you're driving the compiler manually.
So given that, I've left out that change for now, and I'm specificially curious about:

@wycats, you were the main proponent of this restriction. Do you have thoughts on the libtest situation?
Do others have opinions on whether or not to outright disallow linking against an explicit panic runtime?

Note that in the revised version of the RFC, you're guaranteed that if you compile a binary with -C panic=foo that you are indeed using the panic runtime for foo if it succeeds. Along those lines you can still guarantee a panic runtime at the application level. The question is just if you should be able to provide that guarantee at the library level as well (like libtest wants).

@alexcrichton

We also chatted about this in the core team meeting and one conclusion we came to was that we may want to simply outright disallow linking to panic runtimes.

I've thought about this some more and come to feel like we ought to continue to allow explicit linking to a runtime (as you ended up with). It feels like a important thing to be able to express sometimes, and it seems like the incentives are mostly aligned to ensure people don't do it when they don't need it.
I think the main fear is that people will "cargo cult" (no pun intended) a dependency on "panic=abort" semantics because they think this will "make their code fast" or perhaps for some other reason (maybe because they fear the code is not exception safe?). Certainly the "make your code fast" concept is just wrong-headed, since -- so long as the final crate chooses panic=abort -- your library is compiled the same way, whether or not you link to a specific panic runtime.

When writing up this change into the RFC, however, I hit a snag. This details that libtest will explicitly link to panic_unwind because it fundamentally doesn't work with the panic=abort strategy.

Interesting. I was thinking about an example similar to this -- some kind of framework that requires unwinding, but is not itself a final application -- but decided that ultimately the end user is not wrong to use panic=abort, they're just deciding that they want a version of that framework with limited functionality. One could say the same for libtest, but it does seem unlikely anyone wants a test harness that aborts on the first failure and which doesn't support "should-panic" tests. :)

So I've been thinking about the needs/provides thing a bit more. I think I've decided @alexcrichton had the correct design all along, but I might propose some alternative terminology, and elaborate on how I view the design.
I think we can generalize these concepts to something I will call a "global service". The basic idea is that there are some "global services" that the standard library employs but which, ultimately, are best defined at the application level, not by any specific library:

Core panic: this service is used to initiate a panic from within the core crate; it accepts a limited set of argument types.
Std panic: this service is used to initiate a panic from within libstd; it accepts a wider set of argument types.
Allocator: this service is used to allocate memory.

Now a "service" sounds all abstract but all that it really amounts to is some kind of undefined, extern reference with a well-known name, roughly like this:
extern fn core_panic(...);

The compiler doesn't really have to know about these signatures in particular. These methods are always invoked by library code: if the compiler wants to use a service, e.g. to initiate a panic for an array indexing failure, it calls a lang item (in this case, in libcore) which invokes the service.
Each service is tied to a particular crate that defines its interface (that is, the signature above). This crate -- and only this crate -- is tagged with an attribute I am going to call #[service_interface(foo)]. So This is the equivalent of #[needs] today, so #[service_interface(std_panic)] might replace #[needs_panic_runtime]. To "implement" a service you make a crate that defines the required fn(s) and contains the attribute #[service_impl(std_panic)].
The role of the attributes is two-fold:

They do help with error messages. In particular, if you have a dependency on a "service-interface" crate, but there is no corresponding "service-impl" crate, the compiler can give a nice error. This error would be reported whenever we produce a final binary artifact.
They instruct the compiler to "auto-inject" dependencies when needed. The idea is that when we produce a final artifact, if a given service-impl is missing, the compiler will provide a suitable default based on its current settings.

The last point is important given that some users will "opt out" of libstd and hence not require the full suite of services. For example, if I am writing a crate that carefully avoids liballoc, then when I produce my binary, I have never added a dependency on the allocator service. This means the compiler can simply inject no allocator at all. Similarly, if I avoid libstd, I might never need the "std panic" service.
Note that the "core panic" service is ALWAYS needed, because you can't opt out of libcore. But you should be able to define it yourself (eventually, anyway). So, in a kernel setting for example, this might be just a fn that invokes the kernel's "oops" handler.
OK, does this make sense to people? @alexcrichton does this capture your intentions?
Also, what do you guys think of the proposed "interface/impl" terminology? My goal there is to avoid the implication that one writes #[needs_panic_runtime] to indicate that you need unwinding to work properly. This is not what the attribute means, but I feel like it is what it sounds like.

I think the main fear is that people will "cargo cult" (no pun intended) a dependency on "panic=abort" semantics because they think this will "make their code fast" or perhaps for some other reason.

I just remembered that @wycats was also concerned that we might want the "requires runtime X" limitation to be surfaced in the cargo metadata, so it can be accounted for in package resolution. I'm still not sure if I agree with this, but it's worth discussing a bit more.

I think we can generalize these concepts to something I will call a "global service".

OK, one last note on this. We could also keep this same 'service' concept, but build it up with lang items. The idea would be that the "service interface fn" is one lang item, and there is a separate lang item for the impl. The "interface" crates would define the interface lang item, and the "impl crates" would define the impl lang item. The existing lang item uniqueness requirements would then ensure you have at most one impl crate. The compiler itself would understand that, if you have define the service lang item, either the impl lang item must be defined, or we must inject a suitable impl crate to define it.
This then becomes a "pattern" on lang items rather than a "thing" the compiler supports. I think leveraging lang items might make sense if we don't plan to allow users to maybe eventually define their own services. That is, as long as all the services are defined in libstd, then having explicit "service" attributes might be overkill.

@nikomatsakis

One could say the same for libtest, but it does seem unlikely anyone wants a test harness that aborts on the first failure and which doesn't support "should-panic" tests. :)

Which situation are you exactly considering here? If I have a crate that "panic=abort"s , I still want to test that it aborts when it needs to, that is, I still want to test the panics.
In the same way, a library in which "panic=unwind" might still have some code that directly aborts without unwinding (there are multiple valid reasons for doing this in some situations), and I would like to be able to tests those aborts as well.
That is: even if "panic=unwind" I typically want to test panics (with unwinding), aborts (without unwinding), and in debug mode "debug traps" like asserts. Libtest should be able to handle all these cases. So therefore it should also be able to test aborts in a library that "panic=abort"s. Or what am I missing?

@nikomatsakis

The last point is important given that some users will "opt out" of libstd and hence not require the full suite of services. For example, if I am writing a crate that carefully avoids liballoc, then when I produce my binary, I have never added a dependency on the allocator service. This means the compiler can simply inject no allocator at all.

This sounds great. It would be awesome if one could enforce the lack of a service (to help those that e.g. want to avoid liballoc) as well as query the compiler for the services available and which code requires them (to help those that e.g. have a crate that depends on liballoc but want to remove this dependency).

@gnzlbg

Which situation are you exactly considering here? If I have a crate that "panic=abort"s , I still want to test that it aborts when it needs to, that is, I still want to test the panics.

I was mostly commenting on how libtest works today, which is to spawn threads for all of your #[test] functions and execute them. What you are pointing out is that this impl technique isn't really robust -- we ought to spawn processes, if we want to consider the possibility of something that might abort.
Put another way, we should probably modify libtest to be compatible with "panic=abort", long term. This might involve a #[cfg] switch that switches over to using processes instead of threads (that will typically be much slower, I expect, but maybe I am wrong), and perhaps a way to specify for particular tests that they will abort and hence require a process even when we are using unwinding.

@gnzlbg

This sounds great. It would be awesome if one could enforce the lack of a service (to help those that e.g. want to avoid liballoc) as well as query the compiler for the services available and which code requires them (to help those that e.g. have a crate that depends on liballoc but want to remove this dependency).

This is another condition I didn't add, actually, but at least for the impl of a service, it should have no dependency on the service interface (or else we risk circular execution). There's not much we can do about this for the "core panic" service though I suppose (but ... oh well. Don't do that.)
Anyway, it seems like being able to assert that some crate is NOT in your dependency graph might be a generally useful cargo feature, but and would suffice here, since each service would have a single well-defined interface crate.

I was mostly commenting on how libtest works today, which is to spawn threads for all of your #[test] functions and execute them. What you are pointing out is that this impl technique isn't really robust -- we ought to spawn processes, if we want to consider the possibility of something that might abort.

This exactly the reason why the only party capable of selecting the panicking mechanism in the first place should be the builder of final executable. --test would then produce binaries which would unconditionally use panic=unwind.

quickcheck uses the same technique as libtest to witness failures caused by panics. Switching to a process model there would be trickier, but probably possible albeit slower.

This precisely the reason why the only party capable of selecting the panicking mechanism in the first place should be the builder of final executable. --test would then produce binaries which would unconditionally use panic=unwind.

Well, this is precisely the point -- if you write an application that is intended to panic on aborts, and takes advantage of this in some way, you couldn't test it with --test, which requires panic=unwind. So I guess yet more reason to ensure you are compatible with both modes, even if you use abort when shipping. :)

@nikomatsakis: regarding the abort tests, I'd like to point out "prior art" in Google Tests Death Tests. A process is spawned for each specific death test (see gtest-death-test-internals.h), which has the benefit of respecting the "You don't pay for what you don't use" philosophy.

@nikomatsakis I very much like what you have outlined here https://github.com/rust-lang/rfcs/pull/1513#issuecomment-192256091. Regarding your follow up comment my hunch would actually be to go the other way and make make lang items "special" services, with the interface defined in the compiler instead of another crate.
I'm now leaning towards just nailing down services for now, and worrying about how Cargo should expose them later. In the vain of Cargoizing the standard library, I'd love for anybody to be able to define a service (in Rust, and perhaps make note of it in the Cargo file), and then the standard library becomes just a normal user of that functionality. I suppose it's fine to adopt something along the lines of what is proposed temporarily for Cargo, but stabilizing it well bake in what might be an avoidable special case.

@nikomatsakis
Thanks for all the comments! Your thoughts on services I believe are precisely what I was intending with #![allocator] and #![panic_runtime]. Some thoughts of mine (perhaps somewhat out of order):

It's tough to see how this would extend to user-defined services (or something like that). One of the crucial pieces of a service is the compiler's selection of a default. I wonder if this could just be part of the service_interface declaration? Declaration of a default service as well?
The default service for a panic runtime is unfortunately a little trickier though as it's connected to codegen (however the final artifact is produced).
The circular requirement I've thought in the past is somewhat interesting. It's obvious for allocators (an allocator can't be built on data structures which require itself as an allocator). It's less obvious for panic runtimes, however. You've pointed out you can't not rely on core, and it just means that panicking while panicking needs to be handled. I guess arguably you could just panic on allocating while allocating? I wonder if services should just be configurable as to whether they can have circular deps or not (or maybe the service_impl indicates whether it's cyclic compatible and the compiler thinks it's not by default)
The last mile in the stabilization of libcore I think will be stabilizing the ability to define your own core panic runtime (right now this is unstable). I'd be somewhat wary of using lang items for services because I would like to keep all lang items unstable except for this one, which seems weird. Stabilizing parts of a services interface, however, may make sense for libcore.

Ok, that may be a bit rambly, but overall I think there's lots of good ideas here for extending the #![foo] and #![needs_foo] support here for panic runtimes and allocators. I'm confident that all the relevant pieces of the design are sufficiently unstable to give us all the flexibility we need to change names/defaults/implementations in the future.

I just remembered that @wycats was also concerned that we might want the "requires runtime X" limitation to be surfaced in the cargo metadata, so it can be accounted for in package resolution.

To elaborate a little more on this, one could imagine that 1.2.0 of a library didn't link to a panic runtime, but 1.3.0 linked to an abort runtime. If your application requires an unwinding runtime (which Cargo knows through panic = 'unwind' in Cargo.toml), then Cargo should in theory choose 1.2.0 over 1.3.0.
I agree with @nikomatsakis though that pushing this kind of logic into Cargo's resolver is... unappetizing at least. It doesn't seem like that kind of situation will really be that common to justify the complexity for pushing it through to Cargo.

@gnzlbg, @nikomatsakis, @BurntSushi, @nagisa, @matthieu-m
er well... everyone talking about testing!
I think that @nikomatsakis was spot on in realizing that a panic=abort copy of libtest would be next-to-useless in today's incarnation. If no test fails, then it's actually fine, but the whole point of testing is that they sometimes fail! With panic=abort then #[should_fail] tests definitely can't be supported and failing tests would also look quite bad.
It's a good point, however, that this is just an artifact of today's implementation. The suite could have switches for spawning processes for #[should_fail] tests unconditionally and speculatively only spawning threads for tests that should pass otherwise. There are also good points about how spawning process can be, erm, slow! The crux of this, however, is that the current implementation requires panic_unwind which needs to be expressed somehow. This is likely not the only situation where this comes up, and being able to express that desire seems important to me.
So in general while we certainly can revamp libtest to support processes and test aborting code, it seems like that'd be a long road and relatively difficult to do. In the meantime we can rest easy knowing that tests will continue to work as before as all code linking to libtest must have panic=unwind.

To just lay out some ideas on how to generalize, we could have a way for a Cargoized library to insist that it, and everything that is linked to it, is compiled with some codegen option (or other compiler option). Then the unwinding panic implementation crate can use that to insist that all the crates that are linked with it are compiled with landing pads etc.
I like this because on some level the code gen aspect, and service aspect, of panicking strategies are different beasts. Services, and "propagated mandatory options", tackle a generalized form of each separately, while allowing the coordination of the two to emerge as a pattern.

On the topic of tests, I've long wanted to try write a test handler for kernel unit tests. Clearly, if that's even possible, the exact strategy will vary from kernel to kernel, and thus users would need to be able to write their own harness. If we end up supporting that, letting the custom harness specify a panicking strategy, like any other executable, should be doable. Ergo, I think we can be optimistic that testing as a special case for this will go away eventually.

@alexcrichton

It's tough to see how this would extend to user-defined services (or something like that). One of the crucial pieces of a service is the compiler's selection of a default. I wonder if this could just be part of the service_interface declaration? Declaration of a default service as well?

I didn't really mean for that to be a "complete proposal". I am skeptical of the idea of user-defined services anyway -- by definition these "services" are global, and that seems to be kind of anti-composable. That is, all the problems we're talking about with respect to splitting the ecosystem seem to be multiplied if we let users select their own services. (Basically because the more services there are, the more chance of a problem.)
For example, imagine someone defined a "logging" service. Then someone else might write a crate that depends on a particular kind of logger. And now you have an application that wants to use a different kind of logging. Stuck.
I'd much rather we stick to traits as the means of "injecting" code from outside. If there are problems with using crates for these purposes -- and there are -- we should maybe try to address those problems instead.
Still, I found it helpful to work through the exercise.

I'd be somewhat wary of using lang items for services because I would like to keep all lang items unstable except for this one, which seems weird.

Why does that seem wrong? I definitely expect to stabilize lang items piecemeal, not all at once. I mean, imagine we add a new feature that adds a lang item -- that lang item would surely not be "insta-stable" just because we stabilized some other feature that had a lang item?
I think what's a bit more unclear is whether it's worth having many kinds of "lang item-like" things or whether we should just have one category. For example, I personally think I would prefer to remove the concept of "intrinsics" and just have those be lang items too. Basically lang item would mean "something the compiler treats specially in some way". But it may also be nice to have categories for different purposes:

intrinsics: functions whose body the compiler will synthesize
lang items: functions the compiler will call
services: stuff where the compiler will inject a crate

It's kind of "six in one, half a dozen in the other" I guess. Note that we already have lang items that don't fit the above description though: e.g., the Sized trait, where we both automatically generate Sized requirements and generate the impls.
As I said, I personally don't really see what having these categories buys us. It seems like more opportunity to debate and argue about what category to put things in, instead of just arguing about what we want it to do.

To elaborate a little more on this, one could imagine that 1.2.0 of a library didn't link to a panic runtime, but 1.3.0 linked to an abort runtime. If your application requires an unwinding runtime (which Cargo knows through panic = 'unwind' in Cargo.toml), then Cargo should in theory choose 1.2.0 over 1.3.0.

This example still seems odd to me. I feel like adopting a particular panic strategy would have to be a breaking change. Presumably, then, the older code actually DID depend on a particular strategy, but just didn't declare it -- or else the author used semver wrong. If they are using semver wrong, then we can always have resolution failures. And if adding this dependency was a bug fix, then you are getting an error for a reason -- just as if they added a forgotten T: Send bound that was needed for soundness, and you are now having a compilation problem.

@nikomatsakis

I'd much rather we stick to traits as the means of "injecting" code from outside.

I've actually thought from time to time that the only reason we have this problem is we're distributing binaries. For example if everyone always compiled libstd from scratch then we'd probably just use #[cfg] and something like Cargo features to take care of this problem.
Which I guess is a way of saying that it makes sense to me that this problem is "special" to libstd today because it's the only crate that's distributed in a binary form. Not sure if this is an insight or leads to much though :)
Also about lang items, seems fine to me if we're otherwise fine having some stable and some unstable lang items. I do like the idea of unifying at least the concept of everything in the compiler.

intrinsics: functions whose body the compiler will synthesize
- lang items: functions the compiler will call
- services: stuff where the compiler will inject a crate

When thought of as different types of services:

compiler-defined implementation: today's intrinsics
compiler-defined interface: today's lang items
otherwise: "normal" services.

@alexcrichton

I've actually thought from time to time that the only reason we have this problem is we're distributing binaries. For example if everyone always compiled libstd from scratch then we'd probably just use #[cfg] and something like Cargo features to take care of this problem.

Which I guess is a way of saying that it makes sense to me that this problem is "special" to libstd today because it's the only crate that's distributed in a binary form. Not sure if this is an insight or leads to much though :)

I have a few concerns about cfg vs other mechanisms from https://github.com/rust-lang/rfcs/pull/1513#issuecomment-190027818, though they apply more to allocators, "libsys" experiments, and core panicking, than std panicking. But as you know from https://github.com/rust-lang/rfcs/pull/1133 I am general very supportive of this "carogize libstd removes problems" line of of thinking :).

@alexcrichton

I've actually thought from time to time that the only reason we have this problem is we're distributing binaries. For example if everyone always compiled libstd from scratch then we'd probably just use #[cfg] and something like Cargo features to take care of this problem.

I partly agree. I do imagine that we'll always want to give people the option of picking their allocator, for example, and it's not clear to me if this could as easily be done with pure cfg and features? Similarly with a kernel implementing the 'core-panic' service.

(Well, I guess we could always have an "open-ended" option that just leaves the symbol dangling, relying on someone else to fill it.)

I was actually thinking that "panic == abort" would indeed be implemented with exit(101) to mirror the program termination semantics that happen today (aborts trigger core dumps). Somewhat debatable, of course!

I feel like exit(101) is not really the right thing: that seems too controlled, and, on the face of it, I would think a contract violation/can't-continue condition that results in the application suddenly dying is when you want core dumps, debuggers to break and OS crash reporting to kick in. If any of these are inappropriate for how your application is using panics (if they're not totally catastrophic failures, e.g. an app that can cope with one of its processes stopping) then one can opt-in to a less severe handler.
As prior art, std::terminate in C++ calls std::abort by default.
On the other hand, users may not be able to do much with any of the results of an abort, so maybe smoothing it over by pretending it's just a "normal" error exit is OK?

Oh I think I mistook C for not dumping core on an assertion failure, but that looks to not be the case. I definitely view panics akin to C assertions, so seems fine to actually call abort in that case.

Cross-posting from dev-platform:
I think for Firefox release builds, we should have the following:

Rust panic!() causes a crash that's MOZ_CRASH()-compatible for crash-reporting purposes. (See MOZ_REALLY_CRASH() for the actual Windows-compatible crashing implementation.)
All Rust code in Gecko, even std, is compiled without unwinding support.
The panic!() reason strings should not end up in the binary. (Neither reason strings from std nor reason strings from elsewhere.)

#1 Is important so that we learn if something that wasn't supposed to be able to happen happened and we see where it happened.
If there's anything to learn about ICU, I think we should try to get the footprint benefits of not having unwinding code and not having the reason strings in the binary before Fennec product management asks whether there's a way to make the APK size impact of the first Rust code smaller.
For debug builds, passing the panic!() reason string to MOZ_CRASH() would be a nice touch, but since the panic!() reason strings are not zero-teminated and MOZ_CRASH() expects zero-terminated reason strings, creating a zero-terminated copy of a string when terminating might not be a good idea. Dunno. (Of course, in theory, rustc could special-case literals as panic!() reasons and add a zero-terminator when compiling in whatever mode Gecko ends up using.)

@hsivonen thanks for the cross post! I'll respond here for now instead of the email thread as this seems relevant to the RFC itself :). As to your requirements:

Rust panic!() causes a crash that's MOZ_CRASH()-compatible for crash-reporting purposes

I think this is achievable through one of two routes. One is that you could install a panic hook which makes an FFI call that calls MOZ_CRASH. The other is that you could use this RFC to implement a custom "panic runtime" where the implementation is the MOZ_CRASH macro instead of calling abort.
The panic hook strategy will still link in runtime support for the standard panic strategy (which in Gecko's case will likely be panic_abort, so not much of a runtime), but it will suffice for ensure that all panics are redirected to MOZ_CRASH. The upside here is that panic hooks are likely to be stable long before implementing your own panic runtime.
The panic runtime strategy would avoid the need to register any hooks and would avoid linking in panic_abort (not that it matters too much anyway).
Overall, I'd recommend the panic hook method for now as it'll likely be stable sooner.

All Rust code in Gecko, even std, is compiled without unwinding support.

This should be possible by compiling everything with -C panic=abort as defined in this RFC. (yay!) We will likely not initially ship this binary of the standard library immediately, but as I understand it Gecko is already compiling OSX rustc/libstd and perhaps MSVC on 32-bit at least, so perhaps it's not that bad for Gecko to build its own libstd?
If this is a problem, however, then we could start putting plans in place to build and distribute a panic=abort standard library via some yet-to-be-designed means (although it will mostly likely involves multirust-rs somehow).

The panic!() reason strings should not end up in the binary

This is an interesting constraint, and one that I hadn't thought of much before. To clarify, this is purely for size benefits? Or is it also avoiding leaking information if a panic happens?
To effectively do this we'd essentially have to redefine the panic! macro in libcore/libstd to ignore the panic message and not pass it to the panic runtime. Essentially, you'd have to recompile libcore/libstd as usual. This isn't currently planned as part of this RFC (and is somewhat orthogonal), but seems doable nonetheless!
How much of a blocker would this be for you (or do you know)? It's probably best to get this into the design pipeline sooner rather than later if possible.

In the case of embedded (microcontroller) use of Rust, one would also want the panic reasons to not be compiled/linked into the final executable, nor any of the other panic code. That is, panic!(whatever) whould be a synonym for abort() in those situations. Note in particular that none of the fmt! machinery should get linked into the final executable either, unless some non-panic! thing uses fmt!.

I think this is achievable through one of two routes. One is that you could install a panic hook which makes an FFI call that calls MOZ_CRASH. The other is that you could use this RFC to implement a custom "panic runtime" where the implementation is the MOZ_CRASH macro instead of calling abort.

I wonder if it made sense to make a custom crash handler more lightweight in terms of ceremony than the proposed panic runtime mechanism (and free of runtime race conditions compared to setting a hook at runtime) by allowing -C panic=abort:foo where foo is the name of a noreturn function that the compiler expects to abort the program.

so perhaps it's not that bad for Gecko to build its own libstd?

I expect it's not that bad, but I don't know. Pinging @luser.

To clarify, this is purely for size benefits? Or is it also avoiding leaking information if a panic happens?

I was treating this as a size concern only.

How much of a blocker would this be for you (or do you know)?

It's hard to say what the blocker factor of optimizing out the binary size cost of unwinding and the reasons strings is. Looking at the ICU sadness with Fennec, one shouldn't expect to get a "you may add this many bytes to the APK" number from the Fennec team, but it would be best to try the first Rust landing on Android with the optimizations already made in the hope that the APK delta from the addition of the first Rust code would fit in the noise and not invite scrutiny.

so perhaps it's not that bad for Gecko to build its own libstd?

I expect it's not that bad, but I don't know. Pinging @luser.

We've done custom builds for various issues already and there's some hope of automating the customizations through taskcluster. I'm most worried about unofficial builds: if building firefox with official rust binaries introduces subtle bugs, we're going to have subtle downstream bugs on developer systems, linux builds, and so on. For this reason I've been trying to move us to upstream toolchain builds whenever possible.

Ralph has done all the actual building of Rust toolchains for Gecko, so take his word for it. I'd definitely echo his sentiment that making it easier to reproduce Firefox builds generally leads to less headaches for us.

@alexcrichton wrote:

To elaborate a little more on this, one could imagine that 1.2.0 of a library didn't link to a panic runtime, but 1.3.0 linked to an abort runtime. If your application requires an unwinding runtime (which Cargo knows through panic = 'unwind' in Cargo.toml), then Cargo should in theory choose 1.2.0 over 1.3.0.

niko mentoned this above, but I wanted to re-iterate the same question: I would have thought that adding a panic = 'unwind', even if you thought that was what you add before implicitly, would be considered requiring a major version bump, not a minor one?
@alexcrichton can you explain why this is not the case?

@hsivonen

by allowing -C panic=abort:foo where foo is the name of a noreturn function that the compiler expects to abort the program.

Perhaps! But in terms of stability this would also likely be unstable for quite some time, so for stable usage you'd probably still want panic hooks. It's true that there's a race, but in theory there's pretty good control over calls into Rust, right? (aka a point known-before all other calls into Rust)

but it would be best to try the first Rust landing on Android with the optimizations already made

Ok, thanks again for the info!
@rillian, @luser

For this reason I've been trying to move us to upstream toolchain builds whenever possible.

Yeah this definitely makes sense to me. Specifically it sounds like for panicking Gecko would desire a standard library which:

Is compiled with panic=abort, removing landing pads
Removes all panic messages, reducing size

While it's certainly possible to do this, the difference may not actually be all that much from using the vanilla builds. You'd have landing pads and panic messages from the standard library only. A good chunk of the standard library is generic code, so it may be the case that the size winnings here would be particularly small. All of Gecko's Rust code would, of course, be compiled with these two options, and that may end up being the lion's share.
In any case, though, that's just postponing the "best solution" longer, which we should definitely have regardless! We'd just probably want to take some more time to ensure that custom-compiled standard libraries like this fit well within our plans for various compilation profiles of the standard library.

@pnkfelix

I would have thought that adding a panic = 'unwind', even if you thought that was what you add before implicitly, would be considered requiring a major version bump, not a minor one?

Ah yes I think @nikomatsakis was referring to the case where if you added extern crate panic_abort that is a breaking change. Adding panic = 'unwind' shouldn't require a major version bump

@alexcrichton ah okay; I misunderstood how profiles work in Cargo.

Hear ye, hear ye! This RFC is entering final comment period.

@alexcrichton

Specifically it sounds like for panicking Gecko would desire a standard library which:

Is compiled with panic=abort, removing landing pads
Removes all panic messages, reducing size

Yes. It's perhaps worth distinguishing, though, between size issues and behaviour issues. It's easier to take special optimization steps on build infrastructure, so a higher bar is possible there. But as you say, this is something that can benefit many projects, so making it easy is advantageous.

@rillian yeah I agree that separating them out is important. Behaviorally I believe Gecko needs no more than what this RFC provides. This provides a guaranteed ability to translate all Rust panics to aborts rather than stack unwinding (even through a custom method via your own panic runtime).
The optimization steps, specifically for size, are tangential infrastructure related problems on our end which we intend to tackle eventually anyway :)

The lang team did not have full quorum this week and will consider this RFC during next week's meeting; remaining in FCP until then.

It seems that there is some kind of false genericity here - you can only really choose between two options, you can't plug in your own strategy. That would require some mechanism for augmenting the compiler (pluggable MIR pass?).
I'm a bit unclear what would happen with -Cpanic=foo. Would the compiler pick one of its codegen strategies and look for a panic_foo crate? Or give an error that foo != {abort | unwind}? I would prefer the latter until we stabilise a mechanism for augmenting the compiler nicely.
However, as a step towards a fully generic mechanism, this RFC looks good. in particular, adopting a somewhat future proof compiler flag. The attributes sound like the right direction too, though it sounds like there will be some future discussion there.

@nrc yeah this isn't intended to be any form of "truly general mechanism" or something like that, the compiler will reject any -C panic=foo arguments when it doesn't recognize foo. One could create multiple -C panic=abort runtimes, however, which differ slightly in exactly how the process is aborted.

Will just libcore have #![needs_panic_runtime]?

No, the standard library will have that attribute as that's where panics are implemented.

The additional lang items for core seems don't strike me as something we want forever. But if this RFC's proposals don't get stabilized until a more general needs/provides mechanism is investigated, I guess this is fine for now.

WRT. the needs_X convention, perhaps a more general solution would be to use the existing trait system:
The idea is that a single downstream crate will be able to "bless" a particular trait implementation. Upstream crates will be able to implicitly use the yet-to-be-determined "blessed" implementation by simply calling the trait methods: Trait::do_x() (or a more explicit syntax, eg. <Blessed as Trait>::do_x() where Blessed is a lang item)
This would normally be an error, because the compiler wouldn't be able to determine which implementation of Trait to use based on the parameter or return types, but the "blessed" trait implementation would act as a fallback in these cases.
The exact mechanism for blessing a trait could vary, but an easy option is to mirror the existing attributes:
#![needs_blessed(Trait)]
#![bless(Trait, Type)] (requires that Type: Trait)
The advantages are extensibility and proper namespacing of blessed items. Additional fun could be had by allowing Trait to be generic...

It's official! The @rust-lang/lang team has decided to accept this RFC.

Tracking issue: https://github.com/rust-lang/rust/issues/32837
If you'd like to keep following the development of this feature, please subscribe to that issue, thanks! :)