Skip to content
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

Closed
11 of 12 tasks
Tracked by #14
aturon opened this issue Jul 17, 2017 · 222 comments
Closed
11 of 12 tasks
Tracked by #14

Tracking issue for RFC 1937: ? in main #43301

aturon opened this issue Jul 17, 2017 · 222 comments
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. E-help-wanted Call for participation: Help is requested to fix this issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. S-tracking-design-concerns Status: There are blocking ❌ design concerns. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@aturon
Copy link
Member

aturon commented Jul 17, 2017

This is a tracking issue for the RFC "? in main" (rust-lang/rfcs#1937).

Steps:

Stabilizations:

Related issues:

Unresolved questions:

@aturon aturon added B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Jul 17, 2017
@ghost
Copy link

ghost commented Jul 17, 2017

How are exit statuses going to be dealt with?

@ketsuban
Copy link
Contributor

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?

@ghost
Copy link

ghost commented Jul 18, 2017

It's not an implementation detail though, is it?

Some scripts use exit codes as a way to get information from a sub-process.

@Screwtapello
Copy link
Contributor

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".

@NoraCodes
Copy link
Contributor

Some scripts use exit codes as a way to get information from a sub-process.

That behavior is always extremely dependent on the program being called except in that non-zero means failure. Given that std::process::exit with a main-function wrapper and a lookup table is going to remain the best option for those who want a more articulate exit status no matter what is done, this seems like a mostly insignificant detail.

@ghost
Copy link

ghost commented Jul 18, 2017

I don't think SemVer has a "mostly insignificant detail" exception though.

@glaebhoerl
Copy link
Contributor

I think the exit code should be added to the unresolved questions list. @zackw also opened a related internals thread.

@Mark-Simulacrum Mark-Simulacrum added the C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. label Jul 27, 2017
@liigo
Copy link
Contributor

liigo commented Aug 11, 2017

Many people agree that the exit code should be 1 on failure (instead of 2):
https://www.reddit.com/r/rust/comments/6nxg6t/the_rfc_using_in_main_just_got_merged/

@nikomatsakis nikomatsakis added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-compiler-middle labels Sep 15, 2017
@nikomatsakis nikomatsakis added this to the impl period milestone Sep 15, 2017
@aturon aturon removed this from the impl period milestone Sep 15, 2017
@arielb1 arielb1 self-assigned this Sep 18, 2017
@bkchr
Copy link
Contributor

bkchr commented Sep 19, 2017

@arielb1 are you going to implement this rfc?

@arielb1
Copy link
Contributor

arielb1 commented Sep 19, 2017

@bkchr

No, just to mentor it. I assigned so I won't forget to write the mentoring notes.

@bkchr
Copy link
Contributor

bkchr commented Sep 19, 2017

Ahh nice, I would be interested in doing it :)
But, I don't have any idea where to start :D

@arielb1
Copy link
Contributor

arielb1 commented Sep 19, 2017

@bkchr

That's why I'm here :-). I should write the mentoring instructions soon enough.

@bkchr
Copy link
Contributor

bkchr commented Sep 19, 2017

Okay, then I'm waiting for your instructions.

@arielb1
Copy link
Contributor

arielb1 commented Sep 19, 2017

Mentoring Instructions

This 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 Termination lang-item to libcore
  • - allow using Termination in main
  • - allow using Termination in doctests
  • - allow using Termination in #[test]

add the Termination lang-item to libcore

First, you need to add the Termination trait to libcore/ops/termination.rs, along with some documentation. You'll also need to mark it as unstable with an #[unstable(feature = "termination_trait", issue = "0")] attribute - this will prevent people from using it before it is stabilized.

Then, you need to mark it as a lang-item in src/librustc/middle/lang_items.rs. This means the compiler can find it out when type-checking main (e.g. see 0c3ac64).
That means:

  1. adding it to the lang-items list (in librustc/middle/lang_items.rs)
  2. adding a #[cfg_attr(not(stage0), lang = "termination")] to the Termination trait. The reason you can't just add a #[lang = "termination"] attribute is because the "stage0" compiler (during bootstrapping) won't know termination is something that exists, so it won't be able to compile libstd. We'll manually remove the cfg_attr when we update the stage0 compiler.
    See bootstrapping docs at XXX for details.

allow using Termination in main

This is the interesting part I know how to deal with. This means making a main that returns () not type-check (currently you get a main function has wrong type error) and work.

To make it type-check, you first need to remove the existing error in:

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:

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:

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.

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 ().

@arielb1
Copy link
Contributor

arielb1 commented Sep 19, 2017

@bkchr

Can you at least join the IRC/gitter?

@nikomatsakis nikomatsakis added E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. and removed E-needs-mentor labels Sep 21, 2017
@nikomatsakis
Copy link
Contributor

nikomatsakis commented Sep 25, 2017

@bkchr just checking in -- I saw you and @arielb1 were conversing on gitter some time back, any progress? Get suck somewhere?

@bkchr
Copy link
Contributor

bkchr commented Sep 25, 2017

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.

@yaahc
Copy link
Member

yaahc commented Feb 9, 2022

For those who are watching this thread: We've begun the FCP stabilizing Termination and ExitCode in #93840

@bphd
Copy link

bphd commented Feb 22, 2022

For those who are watching this thread: We've begun the FCP stabilizing Termination and ExitCode in #93840

I hoped while heading here that it was a long time implemented function and that the documentation wasn't up to date. But, well...

Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Mar 29, 2022
…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.
@Stargateur
Copy link
Contributor

Stargateur commented Apr 6, 2022

My opinion after reading through the IRLO thread that I linked above is that we probably want to provide a more explicitly cross-platform API for working with exit codes than what i32 provides, since there seem to be significant hazards with just passing out arbitrary i32s and hoping they mean the same thing on every platform. As such, I think we should change Termination::report to return exit code rather than an i32 since it gives us significantly more flexibility going forward to evolve the API or change the representation in platform specific ways. Unfortunately, we can't also make this change for the std::process::exit function but assuming I am correct and understanding the footguns around exit codes, I don't think we want to perpetuate that decision just in the name of consistency, and I can easily imagine us adding a method to the exit code type that acts as an alternative to the exit function.

I agree very much, if we take C, it's pretty much implementation behaviour to return any value other than EXIT_SUCCESS and EXIT_FAILURE. Unfortunately it seems that over time we found out it's impossible to unify exit code of program even just taking linux. I believe keep a failure or success approach is probably the best instead of allowing user to return custom i32 that wouldn't mean much for user.

@yaahc
Copy link
Member

yaahc commented Apr 6, 2022

I agree very much, if we take C, it's pretty much implementation behaviour to return any value other than EXIT_SUCCESS and EXIT_FAILURE. Unfortunately it seems that over time we found out it's impossible to unify exit code of program even just taking linux. I believe keep a failure or success approach is probably the best instead of allowing user to return custom i32 that wouldn't mean much for user.

We ended up also adding From<u8> for ExitCode since that range seemed to be fairly consistently supported across all platforms so we're already looking at supporting more than a binary SUCCESS / FAILURE. I'd be curious to know more about what issues you had with linux to see if it's new information and if they would make us want to reconsider that decision before Termination lands on stable.

@Stargateur
Copy link
Contributor

Stargateur commented Apr 6, 2022

I'd be curious to know more about what issues you had with linux to see if it's new information

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 u8 Rust would probably need to state what it's mean and this would probably fall to "0 for success any other value is failure". Unless we have a RFC that could be used to introduce some standard and clever universal error code value I don't see the point to have this feature for now.

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. (also if needed one can just use process::exit())

@nixpulvis
Copy link

nixpulvis commented Apr 6, 2022

I don't see why something called Termination or ExitCode would fundamentally need to be restricted to only talking about a binary SUCCESS or FAILURE, if that's what you mean @Stargateur. It's just another part of the external interface a program can define for itself.

Given my manual's definition of exit (from man 3 exit):

NAME
       exit - cause normal process termination

SYNOPSIS
       #include <stdlib.h>

       noreturn void exit(int status);

I could, for example, make an executable named foo which I define to exit with a code of 0 for success, positive numbers for the number of seconds it took to calculate something, and negative numbers for various errors. I will note, as others have mentioned, that many shells (and other programs) treat anything other than 0 as an error. Should that mean we only have two options... no, because at the very least there are kinds of errors to consider.

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

@Stargateur
Copy link
Contributor

Stargateur commented Apr 6, 2022

I don't see why something called Termination or ExitCode would fundamentally need to be restricted to only talking about a binary SUCCESS or FAILURE, if that's what you mean @Stargateur.

if that trouble you we could rename it. ExitStatus. (also it's implementation behaviour to return any value other than 0 EXIT_SUCCESS or EXIT_FAILURE with exit() with C)

I could, for example, make an executable named foo which I define to exit with a code of 0 for success, positive numbers for the number of seconds it took to calculate something, and negative numbers for various errors. I will note, as others have mentioned, that many shells (and other programs) treat anything other than 0 as an error. Should that mean we only have two options... no, because at the very least there are kinds of errors to consider.

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 your_prog && ls for no reason. It's way better to use stdout to return result. It's can be piped, used, manipulate, parsed.

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

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.

@nixpulvis
Copy link

nixpulvis commented Apr 6, 2022

The shell's usage of && is a fantastic argument.

Are there any platforms with an exit function that has the following declaration (sorry if I'm out of the loop)?

noreturn void exit(boolean failure);

@yaahc
Copy link
Member

yaahc commented Apr 7, 2022

The shell's usage of && is a fantastic argument.

Are there any platforms with an exit function that has the following declaration (sorry if I'm out of the loop)?

noreturn void exit(boolean failure);

Wasm is the closest example I can think of (at least i think sys/unsupported is only for wasm).

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 ExitCode(bool) impl. Not sure about how this translates to actual system APIs though. I don't really know how the #[lang = "start"] lang item gets wired up to actual system APIs so I'm gonna stop here and leave tracing it further as an exercise for the reader (most likely future me).

@joshtriplett
Copy link
Member

joshtriplett commented Apr 7, 2022 via email

@Stargateur
Copy link
Contributor

Stargateur commented Apr 7, 2022

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.

As I explain stderr would do a better job doing that. In final user will not do a echo $? to know, they would just read stderr.

@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 exit() is enough if people really really want this feature (and I expect that people would use exit anyway if they want return custom code). We don't need to include it by default in the language itself aka the main().

@joshtriplett
Copy link
Member

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.

As I explain stderr would do a better job doing that. In final user will not do a echo $? to know, they would just read stderr.

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.

@Stargateur
Copy link
Contributor

Stargateur commented Apr 7, 2022

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 exit() do that. If the subprocess fail it's the user you want to inform.

I think we could have @BurntSushi input on error code since he is the creator of ripgrep that have a custom error code like grep (legacy reason I guess)

I think avoid a implemented behaviour in main return for Rust is better.

@joshtriplett
Copy link
Member

joshtriplett commented Apr 7, 2022

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.

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.

well obviously I would inform the user with "the subprogram fail".

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.

In the end having return code to 127 change nothing and add no information.

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.

@Stargateur
Copy link
Contributor

@joshtriplett Why would you oppose to let such scenario be handle by exit() feature ?

@joshtriplett
Copy link
Member

Why would you oppose to let such scenario be handle by exit() feature ?

Because exit codes are a well-established mechanism that I'd like to see integrated with ?-based error handling, and I don't see any reason not to offer first-class support for them. And because exit doesn't necessarily allow a program's cleanup to run, whereas returning from main does.

@Kixunil
Copy link
Contributor

Kixunil commented Apr 7, 2022

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 grep is documented so the code above is sensible. Pulling this out of stderr would be very annoying and possibly unreliable. A Rust program should be able to support something similar.

@lnicola
Copy link
Member

lnicola commented Apr 7, 2022

Pulling this out of stderr would be very annoying and possibly unreliable.

And error messages are often localized.

@BurntSushi
Copy link
Member

I think we could have @BurntSushi input on error code since he is the creator of ripgrep that have a custom error code like grep (legacy reason I guess)

Here's the relevant code:

https://github.com/BurntSushi/ripgrep/blob/ced5b92aa93eb47e892bd2fd26ab454008721730/crates/core/main.rs#L67-L73

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 ?-in-main doesn't support returning exit codes beyond "success" or "failure," then yeah, I would just keep using process::exit.

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 0 tends to mean success, but there is otherwise no agreed upon standard for what other numbers mean. So yeah, I think we should at minimum provide a way for constructing an ExitCode from a u8.

@Lucretiel
Copy link
Contributor

Lucretiel commented Apr 7, 2022

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.

@mathstuf
Copy link
Contributor

mathstuf commented Apr 7, 2022

It's also relevant to skipping tests in various test suites (autoconf and automake use 77 to indicate that a test should skip. See this pre-RFC IRLO thread for more prior art there.

@Stargateur
Copy link
Contributor

Stargateur commented Apr 7, 2022

Because exit codes are a well-established mechanism that I'd like to see integrated with ?-based error handling, and I don't see any reason not to offer first-class support for them.

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.

And because exit doesn't necessarily allow a program's cleanup to run, whereas returning from main does.

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 exit().

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 ?-in-main doesn't support returning exit codes beyond "success" or "failure," then yeah, I would just keep using process::exit.

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 count=$(grep --count "$needle" "$haystack_file") and if [ "$count" -eq "0" ];. (combine with an option that stop at first result if you only want to know if there is at least one match)

And error messages are often localized.

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 exit() and that I think they are better way to report result than using error code. I'm also afraid this would be used badly. But I don't strongly oppose, I just raise concern. And I'm quite alone in this team so be it.

@yaahc
Copy link
Member

yaahc commented Apr 7, 2022

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

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. i32 is the defacto interface by which processes report errors to each other on most platforms and while I would love to see that replaced by something better in the future, I do not think parsing stderr is the fix.

@Kixunil
Copy link
Contributor

Kixunil commented Apr 8, 2022

even on linux it's a meh and some system doesn't support them at all

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 set -e is <redacted slur> error handling. (I have some unpleasant experiences with it and generally avoid it.)

@yaahc
Copy link
Member

yaahc commented Apr 8, 2022

@Kixunil please refrain from using ableist slurs.

@Stargateur
Copy link
Contributor

Stargateur commented Apr 8, 2022

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.

Sorry I wasn't clear, linux only take 8 bits of int of exit(), that why the current plan is to impl From<u8> for ExitCode. This is because linux use the rest of the int as bitfield for other info. Windows doesn't do that so you don't have same value of linux for anything > 255 and negative value are unclear too. Here we have a big difference between two major OS, then yaahc said "i32 is the defacto interface by which processes report errors to each other on most platforms" that IMO false it's a int so this isn't clear about being a i32 or other int size. Specially on embedded system where int could be 16 bits. For system that doesn't support it I don't have a list but yaahc said Wasm seem to not allow it.

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.

Agree with @yaahc that set -e is error handling.

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 if in bash is complicated.

@yaahc
Copy link
Member

yaahc commented Apr 8, 2022

I'm not sure I understand that 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 if in bash is complicated.

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.

@joshtriplett
Copy link
Member

Every usage of this tracking issue in rust-lang/rust has been stabilized. There may be further work we want to do on ? in main in the future, but the work tracked by this tracking issue seems to be done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. E-help-wanted Call for participation: Help is requested to fix this issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. S-tracking-design-concerns Status: There are blocking ❌ design concerns. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests