-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking issue for RFC 1937: ?
in main
#43301
Comments
How are exit statuses going to be dealt with? |
This comment by @Screwtapello seems to have been made too close to the end of FCP for any alterations to be made to the RFC in response to it. In short: the RFC proposes returning 2 on failure on grounds which, while well-founded, are obscure and produce a slightly unusual result; the least surprising thing is to return 1 when the program has no indication it wants any more detail than just success or failure. Is this sufficiently bikesheddy that it can be discussed without it feeling like we're perverting the RFC process, or are we now locked into this specific implementation detail? |
It's not an implementation detail though, is it? Some scripts use exit codes as a way to get information from a sub-process. |
This is specifically about the case when a sub-process (implemented in Rust) has no information to give, beyond a binary "everything's fine"/"something went wrong". |
That behavior is always extremely dependent on the program being called except in that non-zero means failure. Given that |
I don't think SemVer has a "mostly insignificant detail" exception though. |
I think the exit code should be added to the unresolved questions list. @zackw also opened a related internals thread. |
Many people agree that the exit code should be |
@arielb1 are you going to implement this rfc? |
No, just to mentor it. I assigned so I won't forget to write the mentoring notes. |
Ahh nice, I would be interested in doing it :) |
That's why I'm here :-). I should write the mentoring instructions soon enough. |
Okay, then I'm waiting for your instructions. |
Mentoring InstructionsThis is a WG-compiler-middle issue. If you want to seek help, you can join #rustc on irc.mozilla.org (I'm arielby) or https://gitter.im/rust-impl-period/WG-compiler-middle (I'm @arielb1 there). There's a WIP compiler readme at #44505 - it describes some things in the compiler. Work plan for this RFC:
add the
|
fn check_main_fn_ty<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, | |
main_id: ast::NodeId, | |
main_span: Span) { | |
let main_def_id = tcx.hir.local_def_id(main_id); | |
let main_t = tcx.type_of(main_def_id); | |
match main_t.sty { | |
ty::TyFnDef(..) => { | |
match tcx.hir.find(main_id) { | |
Some(hir_map::NodeItem(it)) => { | |
match it.node { | |
hir::ItemFn(.., ref generics, _) => { | |
if generics.is_parameterized() { | |
struct_span_err!(tcx.sess, generics.span, E0131, | |
"main function is not allowed to have type parameters") | |
.span_label(generics.span, | |
"main cannot have type parameters") | |
.emit(); | |
return; | |
} | |
} | |
_ => () | |
} | |
} | |
_ => () | |
} | |
let se_ty = tcx.mk_fn_ptr(ty::Binder( | |
tcx.mk_fn_sig( | |
iter::empty(), | |
tcx.mk_nil(), | |
false, | |
hir::Unsafety::Normal, | |
Abi::Rust | |
) | |
)); | |
require_same_types( | |
tcx, | |
&ObligationCause::new(main_span, main_id, ObligationCauseCode::MainFunctionType), | |
se_ty, | |
tcx.mk_fn_ptr(tcx.fn_sig(main_def_id))); | |
} | |
_ => { | |
span_bug!(main_span, | |
"main has a non-function type: found `{}`", | |
main_t); | |
} | |
} | |
} |
Then, you need to add a check that the return type implements the Termination
trait in (you add a trait obligation using register_predicate_obligation
- search for uses of that). That can be done here:
rust/src/librustc_typeck/check/mod.rs
Lines 1100 to 1108 in 9a00f3c
let mut actual_return_ty = coercion.complete(&fcx); | |
if actual_return_ty.is_never() { | |
actual_return_ty = fcx.next_diverging_ty_var( | |
TypeVariableOrigin::DivergingFn(span)); | |
} | |
fcx.demand_suptype(span, ret_ty, actual_return_ty); | |
(fcx, gen_ty) | |
} |
The other part is making it work. That should be rather easy. As the RFC says, you want to make lang_start
generic over the return type.
lang_start
is currently defined here:
Line 32 in 9a00f3c
fn lang_start(main: fn(), argc: isize, argv: *const *const u8) -> isize { |
So you'll need to change it to be generic and match the RFC:
#[lang = "start"]
fn lang_start<T: Termination>
(main: fn() -> T, argc: isize, argv: *const *const u8) -> !
{
use panic;
use sys;
use sys_common;
use sys_common::thread_info;
use thread::Thread;
sys::init();
sys::process::exit(unsafe {
let main_guard = sys::thread::guard::init();
sys::stack_overflow::init();
// Next, set up the current Thread with the guard information we just
// created. Note that this isn't necessary in general for new threads,
// but we just do this to name the main thread and to give it correct
// info about the stack bounds.
let thread = Thread::new(Some("main".to_owned()));
thread_info::set(main_guard, thread);
// Store our args if necessary in a squirreled away location
sys::args::init(argc, argv);
// Let's run some code!
let exitcode = panic::catch_unwind(|| main().report())
.unwrap_or(101);
sys_common::cleanup();
exitcode
});
}
And then you'll need to call it from create_entry_fn
. Currently, it instantiates a monomorphic lang_start
using Instance::mono
, and you'll need to change it to use monomorphize::resolve
with the right substs.
rust/src/librustc_trans/base.rs
Line 697 in 9a00f3c
match et { |
allow using Termination
in doctests
I don't really understand how doctests work. Maybe ask @alexcrichton (that's what I would do)?
allow using Termination
in #[test]
I don't really understand how libtest works. Maybe ask @alexcrichton (that's what I would do)? Unit tests are basically generated by a macro, so you need to change that macro, or its caller, to handle return types that are not ()
.
Can you at least join the IRC/gitter? |
No sorry, no progress up to now. Currently I have a lot of things to do, but I hope that I will find some time this week to start on this. |
For those who are watching this thread: We've begun the FCP stabilizing |
I hoped while heading here that it was a long time implemented function and that the documentation wasn't up to date. But, well... |
…ebration-station, r=joshtriplett Stabilize Termination and ExitCode From rust-lang#43301 This PR stabilizes the Termination trait and associated ExitCode type. It also adjusts the ExitCode feature flag to replace the placeholder flag with a more permanent name, as well as splitting off the `to_i32` method behind its own permanently unstable feature flag. This PR stabilizes the termination trait with the following signature: ```rust pub trait Termination { fn report(self) -> ExitCode; } ``` The existing impls of `Termination` are effectively already stable due to the prior stabilization of `?` in main. This PR also stabilizes the following APIs on exit code ```rust #[derive(Clone, Copy, Debug)] pub struct ExitCode(_); impl ExitCode { pub const SUCCESS: ExitCode; pub const FAILURE: ExitCode; } impl From<u8> for ExitCode { /* ... */ } ``` --- All of the previous blockers have been resolved. The main ones that were resolved recently are: * The trait's name: We decided against changing this since none of the alternatives seemed particularly compelling. Instead we decided to end the bikeshedding and stick with the current name. ([link to the discussion](https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/Termination.2FExit.20Status.20Stabilization/near/269793887)) * Issues around platform specific representations: We resolved this issue by changing the return type of `report` from `i32` to the opaque type `ExitCode`. That way we can change the underlying representation without affecting the API, letting us offer full support for platform specific exit code APIs in the future. * Custom exit codes: We resolved this by adding `From<u8> for ExitCode`. We choose to only support u8 initially because it is the least common denominator between the sets of exit codes supported by our current platforms. In the future we anticipate adding platform specific extension traits to ExitCode for constructors from larger or negative numbers, as needed.
I agree very much, if we take C, it's pretty much implementation behaviour to return any value other than |
We ended up also adding |
I don't think it's important information and maybe have been said before. To explain more clearly some program try to return different value for different error on linux, but in final it's mean less cause there is no standard about error code value, so each program ended having its own rules about error code cause each program is very different. I don't think support other value then SUCCESS or FAILURE is worse the pain. If we choose to implement for Thus I don't think we would run in any "problem" with this feature since I don't see any system that wouldn't be able to handle it. But I don't think anybody would ask for such feature anyway, I don't see any real world application. Is there anyone who ask for it ? With your current design of |
I don't see why something called Given my manual's definition of
I could, for example, make an executable named I'll also link to BSD's attempt to come up with some of their own standards here: https://github.com/openbsd/src/blob/master/include/sysexits.h |
if that trouble you we could rename it.
Experience show that use return value is not great, bash script, or any other program that manipulate program launch can't guess return value meaning. Like you say you could use return value for anything you like, the shell would not understand and think your program is failure and stop the && in
My point exactly, there was some attempt but no one really use to any of them, for example does the shell on BSD report "addressee unknown" when a program return 67 ? Stderr is doing a better job allowing to clearly explain what been wrong. In the end failure is any value other than 0. Being able to return any int value from main is IMO something of the past. |
The shell's usage of Are there any platforms with an
|
Wasm is the closest example I can think of (at least i think https://github.com/yaahc/rust/blob/master/library/std/src/sys/unsupported/process.rs#L156-L172 edit: okay actually a lot more platforms than I realized use this as their process impl, not surprised in retrospect. Looks like everything that doesn't identify as unix or windows ends up using the |
On Wed, Apr 06, 2022 at 01:20:02PM -0700, Antoine wrote:
Thus I don't think we would run in any "problem" with this feature
since I don't see any system that wouldn't be able to handle it. But I
don't think anybody would ask for such feature anyway, I don't see any
real world application. Is there anyone who ask for it ? With your
current design of `ExitCode` we can always add it later if there is
people asking for it but I doubt it in 2022.
Yes, programs still need this. While there's no universal standard for
program exit codes, *specific* programs can still define and document
exit codes, and some programs do. This includes both classic UNIX
programs, and current software that needs to distinguish between
various possible outcomes.
As an example, command-line tools that wrap other command-line tools
(e.g. `yourcmd -a -b -c othercmd -x -y -z`) sometimes use specific error
codes for themselves to help distinguish their own failures from the
failures of the specified command. For instance, "this exits with status 127
if it fails to run the specified command, or with the exit status of the
specified command otherwise". (That doesn't compose, but nonetheless it
works better than not having such a distinction at all.)
|
As I explain stderr would do a better job doing that. In final user will not do a @joshtriplett My point is about the usefulness of such feature, I guess people would disadvice to use custom error code in Rust (pretty much what C does). And so shouldn't Rust don't encourage such thing ? As I say I would be open if we do a RFC about standard rusty error code, but today make easy to return error code from Rust main return would just add to the mess of error code in programming. Not to mention we sometime can't honour the behaviour. And I argue that |
Error messages are for humans. Exit codes aren't for humans, they're for programs invoking other programs. Other programs shouldn't (and can't reliably and robustly) parse the human-readable messages printed to stderr to find out what happened; they should check the exit code. |
But as state there no standard, so no way to program handle error code. And here I feel I will have hard time to explain it in english. In your example what the program do ? ok it's the "subprogram" that fail so what ? well obviously I would inform the user with "the subprogram fail". But the shell would not care if that the subprogram that fail or not. In the end having return code to 127 change nothing and add no information. And again I think we could have @BurntSushi input on error code since he is the creator of ripgrep that have a custom error code like I think avoid a implemented behaviour in main return for Rust is better. |
You don't need a standard to handle the exit code of a specific program whose behavior you know. Also, there are various standards (e.g. POSIX or the Single UNIX Specification) for specific programs and their exit codes. Rust implementations of those programs want to maintain compatibility with those standards.
Not all errors are meant to be reported to users. Sometimes programs (or scripts) invoke other programs, and handle the errors of those programs by means other than reporting those errors.
It distinguishes between the exit code of the subprogram and the wrapper tool invoking that subprogram. If you know the behavior of the subprogram, and what exit codes it produces, you can then distinguish between exit codes from the subprogram that indicate distinct cases you want to handle (which aren't always errors, they may be different results distinguished by exit code) from the case of the wrapper tool failing. |
@joshtriplett Why would you oppose to let such scenario be handle by |
Because exit codes are a well-established mechanism that I'd like to see integrated with |
My favorite example when multiple error codes are useful: grep -q "$needle" "$haystack_file"
case $? in
0)
handle_found
;;
1)
handle_not_found
;;
*)
# grep itself failed
exit 1
;;
esac The behavior of |
And error messages are often localized. |
Here's the relevant code: I don't think it's for legacy. @Kixunil gave a good example of why you really want to distinguish between "success," "no match" and "error." If I read the comments above and I don't see any compelling reason for limiting ourselves to "success" or "failure." The docs can clearly state that |
I'll also shout out git bisect, which is the best example I know of a program that consumes error codes from another program. Git bisect wants to find the first commit that fails to pass a test (presumably where a bug was introduced), so you give it a command and it performs a binary search, running the command in a loop on various commits. It interprets 0 as "build success", 125 as "skip / not testable", 1-127 as "build failure", and 128+ as "stop bisecting". It's a really excellent system for tracking down when a particular bug was introduced in a long commit history. |
It's also relevant to skipping tests in various test suites ( |
That false, exit code are not at all well established mechanism, they are on the contrary implemented behaviour so it's hard to rely on them outside of linux and even on linux it's a meh and some system doesn't support them at all. I listed some reason to not offer first class support for them. I think we must clearly document it as implementation behaviour.
My point was to use exit at the end of the main, I think it's should be the same cleanup degree, but agree this is a big con to
I believe grep example is a very bad one of using error code, other are fine but on grep make the program return an error for not found is rather counter intuitive and probably have been source of bug over time. A bash script generally want to stop at any error using: set -e
set -o pipefail Using grep in a bash script with this would make the script stop for empty result, quite weird IMO. Any code/script that want to stop at the first error would be annoying by the grep behaviour. I think an grep option that return the number of match would be better. I don't know if this option exist but something like this
Obviously use stderr in a script require the program to have an option to output machine like error message, json or whatever. Something easy to parse and documented. Quite similar to Cargo metadata output. I didn't say they was no use-case, my point was about let the job be handle by |
I'm gonna go out on a limb and say that this is a shortcoming of error handling in bash, not grep. The fact that bash makes properly handling error cases so incredibly difficult was a big factor in me getting further involved in Rust and Rust error handling in particular. I think you're absolutely right in that the state of exit codes is not okay, but this system exists, people rely on it, and we already support it. |
Can you give some examples? I've been using various Linux distributions for ~14 years and never came across one that didn't support exit codes. It's true that very few programs do stupid shit around exit codes (especially not using them) but programs generally do stupid shit so that's no surprise. By limiting the API we would encourage more stupid shit, not less. Agree with @yaahc that |
@Kixunil please refrain from using ableist slurs. |
Sorry I wasn't clear, linux only take 8 bits of int of In my opinion the current limit with u8 is a nice idea, wasm limit is not a big deal and almost all systems would handle at least u8 value.
I'm not sure I understand the same thing, English is not my best and I have no idea what "I'm gonna go out on a limb " mean. But I believe the point of yaahc was more about bash being bad at error handle in general, even just write a correct |
oh, sorry about that. "going out on a limb" is a saying that refers to "climbing out onto tree branch (limb)" with the idea that the branch would not do a good job supporting you and may break out from under you at any moment. In this situation, the branch represents the position I'm taking or argument I'm making, and I used that phrase to indicate that I felt I was risking push back by being critical of another language and that this is the kind of argument I normally would not make, at least not in issues like this. |
Every usage of this tracking issue in rust-lang/rust has been stabilized. There may be further work we want to do on |
This is a tracking issue for the RFC "
?
inmain
" (rust-lang/rfcs#1937).Steps:
?
inmain
(Implements RFC 1937:?
inmain
#46479)?
in doctest?
in#[test]
Stabilizations:
main
with non-() return types (Stabilizemain
with non-() return types #48453) Merged in Stabilize termination_trait, split out termination_trait_test #49162()
return type #48854)Related issues:
Termination
is ungreat #50291)Unresolved questions:
main
with non-() return types #48453this will be stabilized by Stabilizeno longer true after Restrict the Termination impls to simplify stabilization #48497main
with non-() return types #48453The text was updated successfully, but these errors were encountered: