From 474d93a96ecc7943456642843684da9946fc0fe0 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Mon, 27 Feb 2017 13:20:18 -0500 Subject: [PATCH 1/9] Draft RFC for ? in main --- text/0000-ques-in-main.md | 363 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 text/0000-ques-in-main.md diff --git a/text/0000-ques-in-main.md b/text/0000-ques-in-main.md new file mode 100644 index 00000000000..b066b974cd4 --- /dev/null +++ b/text/0000-ques-in-main.md @@ -0,0 +1,363 @@ +- Feature Name: ques_in_main +- Start Date: 2017-02-22 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allow the `?` operator to be used in `main` and in `#[test]` functions + +To make this possible, the return type of these functions are +generalized from `()` to a new trait, provisionally called +`Termination`. libstd implements this trait for `!`, `()`, `bool`, +`Error`, `Result where T: Termination, E: Termination`, +and possibly other types TBD. Applications can provide impls +themselves if they want. + +There is no magic added to function signatures. If you want to use +`?` in either `main` or a `#[test]` you have to write `-> Result<(), +ErrorT>` (or whatever) yourself. (TBD: It may make sense to provide a +type alias like + + type MayFail = Result<(), Box>; + +which would mean less boilerplate in the most common case for `main`.) + +[Pre-RFC discussion][pre-rfc]. [Prior RFC issue][old-issue]. + +[pre-rfc]: https://internals.rust-lang.org/t/rfc-mentoring-opportunity-permit-in-main/4600 +[old-issue]: https://github.com/rust-lang/rfcs/issues/1176 + +# Motivation +[motivation]: #motivation + +It is currently not possible to use `?` in `main`, because `main`'s +return type is required to be `()`. This is a trip hazard for new +users of the language, and complicates "programming in the small". + +On a related note, `main` returning `()` means that short-lived +programs, designed to be invoked from the Unix shell or a similar +environment, have to contain extra boilerplate in order to comply with +those environments' conventions. A typical construction is + +```rust +fn inner_main() -> Result<(), ErrorT> { + // ... stuff which may fail ... + Ok(()) +} + +fn main() -> () { + use std::process::exit; + use std::io::{Write,stderr}; + use libc::{EXIT_SUCCESS, EXIT_FAILURE}; + + exit(match inner_main() { + Ok(_) => EXIT_SUCCESS, + + Err(ref err) => { + let progname = get_program_name(); + writeln!(stderr(), "{}: {}\n", progname, err); + + EXIT_FAILURE + } + }) +} +``` + +Both of these problems can be solved at once if the compiler and/or +libstd are taught to recognize a `main` that returns +`Result<(), ErrorT>` and supply boilerplate similar to the above. + +# Detailed design +[design]: #detailed-design + +The design goals for this new feature are, in decreasing order of +importance: + +1. The `?` operator should be usable in `main` (and `#[test]` + functions). This entails these functions now returning a richer + value than `()`. +1. Existing code with `fn main() -> ()` should not break. +1. Errors returned from `main` in a hosted environment should + **not** trigger a panic, consistent with the general language + principle that panics are only for bugs. +1. We should take this opportunity to increase consistency with + platform conventions for process termination. These often include + the ability to pass an "exit status" up to some outer environment, + conventions for what that status means, and an expectation that a + diagnostic message will be generated when a program fails + due to a system error. +1. We should avoid making life more complicated for people who don't + care; on the other hand, if the Easiest Thing is also the Right + Thing according to the platform convention, that is better all + around. + +Goal 1 dictates that the new return type for `main` will be +`Result` for some T and E. + +To minimize the necessary changes to existing code that wants to start +using `?` in `main`, T should be _allowed_ to be `()`, but other types +in that position may also make sense. For instance, many +implementations of the [`grep`][grep] utility exit with status 0 when +matches are found, 1 when no matches are found, and 2 when I/O errors +occurred; it would be natural to express that using `Result`. + +The boilerplate shown [above][motivation] will work for any E that is +`Display`, but a tighter bound (e.g. `Error` or `Box`) might +make implementation more convenient and/or facilitate better error +messages. + +[grep]: http://man7.org/linux/man-pages/man1/grep.1.html + +## The `Termination` trait +[the-termination-trait]: #the-termination-trait + +When `main` returns a nontrivial value, the runtime needs to know two +things about it: what error message, if any, to print, and what value +to pass to the platform's equivalent of [`exit(3)`][exit]. These are +naturally encapsulated in a trait, which we are tentatively calling +`Termination`, with this signature: + +```rust +trait Termination { + fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> (); + fn exit_status(&self) -> i32; +} +``` + +The canonical home of this trait is `std::process`. + +`write_diagnostic` shall write a complete diagnostic message for +`self` to its `stream` argument. If it produces no output, there will +be no output. `stream` will normally be the same stream that `panic` +messages from the main thread would go to, which is normally "standard +error". + +The `progname` argument is a short version of the program's name +(abstractly, what you would type at the command line to start the +program, assuming it were in `PATH`; concretely, the basename of +`argv[0]`, with any trailing `.exe` or `.com` chopped off on Windows, +but not on other platforms). This makes it easy to generate +diagnostics in the style conventional for "Unixy" systems, e.g. + + grep: /etc/shadow: Permission denied + +`exit_status` shall convert `self` to a platform-specific exit code, +conveying at least a notion of success or failure. The return type is +`i32` to match [std::process::exit][] (which probably calls the C +library's `exit` primitive), but (as already documented for +`process::exit`) on "most Unix-like" operating systems, only the low 8 +bits of this value are significant. + +(It would probably be a good idea to reexport `libc::EXIT_SUCCESS` and +`libc::EXIT_FAILURE` from `std::process`.) + +Embedded operating systems that have no notion of process exit status +shall ignore the argument to `process::exit`, but shall still call +`write_diagnostic`. Embedded operating systems where returning from +`main` constitutes a _bug_ shall not provide `Termination` at all; +when targeting such systems, the compiler shall insist that the +signature of `main` be `() -> !` instead of `() -> ()`, and `?` shall +continue to be unusable in `main`. + +[std::process::exit]: https://doc.rust-lang.org/std/process/fn.exit.html + +## Standard impls of Termination +[standard-impls-of-termination]: #standard-impls-of-termination + +At least the following implementations of Termination are available in libstd: + +```rust +impl Termination for ! { + fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () + { unreachable!(); } + fn exit_status(&self) -> i32 + { unreachable!(); } +} + +impl Termination for () { + fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () + { } + fn exit_status(&self) -> i32 + { EXIT_SUCCESS } +} + +impl Termination for bool { + fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () + { } + fn exit_status(&self) -> i32 + { if *self { EXIT_SUCCESS } else { EXIT_FAILURE } } + +impl Termination for E { + fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () + { + // unspecified, but not entirely unlike this: + if let Some(ref cause) = self.cause() { + cause.write_diagnostic(progname, stream); + } + writeln!(stream, "{}: {}\n", progname, self.description()); + } + + fn exit_status(&self) -> i32 + { EXIT_FAILURE } +} + +impl Termination for Result { + fn write_diagnostic(&self, progname: &str, stream: &mut Write) { + match *self { + Ok(ref ok) { ok.write_diagnostic(progname, stream); } + Err(ref err) { err.write_diagnostic(progname, stream); } + } + } + fn exit_status(&self) -> i32 { + match *self { + Ok(ref ok) { ok.exit_status() } + Err(ref err) { err.exit_status() } + } + } +} +``` + +If the platform permits, the actual exit status used for `false` will +be different from the exit status used for an `Error`. For instance, +on Unix-like platforms `false` is mapped to status 1 and `Error`s are +mapped to status 2. (This is the convention used by many +implementations of `grep`, allowing it to distinguish "no error but no +matches found" from "an I/O error occurred".) + +(If we wanted to gild the lily, we could map `io::Error`s to specific +codes from [`sysexits.h`][sysexits], but I don't know that we should +bother, given how few other programs do.) + +Additional impls of Termination should be added as ergonomics dictate. +However, there probably _shouldn't_ be an impl of Termination for +Option, because there are equally strong arguments for None indicating +success and None indicating failure. And there probably shouldn't be +an impl for i32 or i8 either, because that would permit the programmer +to return arbitrary numbers from `main` without thinking at all about +whether they make sense as exit statuses. + +[exit]: https://linux.die.net/man/3/exit +[sysexits]: http://www.unix.com/man-pages.php?os=freebsd§ion=3&query=sysexits + +## Implementation issues +[implementation-issues]: #implementation-issues + +The tricky part of implementation is impedance matching between `main` +and `lang_start`. `lang_start` currently receives an unsafe pointer +to `main` and expects it to have the signature `() -> ()`. The +abstractly correct new signature is `() -> &Termination` but I suspect +that this is currently not possible, pending `impl Trait` return types +or something similar. + +Failing that, we _could_ implement the new semantics entirely within +the compiler. When it noticed that `main` returns anything other than +`()`, it would rename the function `inner_main` and inject a shim: + + fn main() -> () { + let term = inner_main(); + term.write_diagnostic(std::env::progname(), std::io::stderr()); + std::process::exit(term.exit_status()); + } + +Similarly for `#[test]` functions. Everything else would then be +straightforward additions to libstd and/or libcore. + +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +This should be taught alongside the `?` operator and error handling in +general. The stock `Termination` impls in libstd mean that simple +programs that can fail don't need to do anything special: + +```rust +fn main() -> Result<(), io::Error> { + let mut stdin = io::stdin(); + let mut raw_stdout = io::stdout(); + let mut stdout = raw_stdout.lock(); + for line in stdin.lock().lines() { + stdout.write(line?.trim().as_bytes())?; + stdout.write(b"\n")?; + } + stdout.flush() +} +``` + +More complex programs, with their own error types, still get +platform-consistent exit status behavior for free, as long as they +implement `Error`. The Book should describe `Termination` to explain +_how_ `Result`s returned from `main` turn into error messages and exit +statuses, but as a thing that most programs will not need to deal with +directly. + +Tutorial examples should probably still begin with `fn main() -> ()` +until the tutorial gets to the point where it starts explaining why +`panic!` and `unwrap` are not for "normal errors". + +# Drawbacks +[drawbacks]: #drawbacks + +Generalizing the return type of `main` complicates libstd and/or the +compiler. It also adds an additional thing to remember when complete +newbies to the language get to error handling. On the other hand, +people coming to Rust from other languages may find this _less_ +surprising than the status quo. + +# Alternatives +[alternatives]: #alternatives + +Do nothing; continue to live with the trip hazard, the extra +boilerplate required to comply with platform conventions, and people +using `panic!` to report ordinary errors because it's less hassle. + +The [pre-RFC][pre-rfc] included a suggestion to use `catch` instead, +but this still involves extra boilerplate in `main` so I'm not +enthusiastic about it. Also, `catch` doesn't seem to be happening +anytime soon. + +# Unresolved Questions +[unresolved]: #unresolved-questions + +We need to decide what to call the new trait. The names proposed in +the pre-RFC thread were `Terminate`, which I like OK but have changed +to `Termination` because return value traits should be nouns, and +`Fallible`, which feels much too general, but could be OK if there +were other uses for it? Relatedly, it is conceivable that there are +other uses for `Termination` in the existing standard library, but I +can't think of any right now. (Thread join was mentioned in the +[pre-RFC][pre-rfc], but that can already relay anything that's `Send`, +so I don't see that it adds value there.) + +I don't know what impls of `Termination` should be available beyond +the ones listed above, nor do I know what impls should be in libcore. +Most importantly, I do not know whether it is necessary to impl +Termination for `Box where T: Termination`. It might be that Box's +existing impl of Deref renders this unnecessary. + +The `MayFail` type alias seems helpful, but also maybe a little too +single-purpose. And if we ever get return type deduction, that would +completely supersede it, but we'd have burned the name forever. + +There is an outstanding proposal to [generalize `?`][try-trait] +(see also RFC issues [#1718][rfc-i1718] and [#1859][rfc-i1859]); I +think it is mostly orthogonal to this proposal, but we should make +sure it doesn't conflict and we should also figure out whether we +would need more impls of `Termination` to make them play well +together. + +Most operating systems accept only a scalar exit status, but +[Plan 9][], uniquely (to my knowledge), takes an entire string (see +[`exits(2)`][exits.2]). Do we care? If we care, what do we do about +it? + +The ergonomics of `?` in general would be improved by autowrapping +fall-off-the-end return values in `Ok` if they're not already +`Result`s, but that's another proposal. + +[try-trait]: https://github.com/nikomatsakis/rfcs/blob/try-trait/text/0000-try-trait.md +[rfc-i1718]: https://github.com/rust-lang/rfcs/issues/1718 +[rfc-i1859]: https://github.com/rust-lang/rfcs/issues/1859 +[Plan 9]: http://www.cs.bell-labs.com/plan9/index.html +[exits.2]: http://plan9.bell-labs.com/magic/man2html/2/exits From fef92e0604fd91ef883f0dff78ad31b04a6c0629 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Wed, 1 Mar 2017 13:11:33 -0500 Subject: [PATCH 2/9] Revisions from first round of feedback --- text/0000-ques-in-main.md | 353 +++++++++++++++++++++++++++----------- 1 file changed, 251 insertions(+), 102 deletions(-) diff --git a/text/0000-ques-in-main.md b/text/0000-ques-in-main.md index b066b974cd4..24d3e919398 100644 --- a/text/0000-ques-in-main.md +++ b/text/0000-ques-in-main.md @@ -6,23 +6,21 @@ # Summary [summary]: #summary -Allow the `?` operator to be used in `main` and in `#[test]` functions +Allow the `?` operator to be used in `main`, and in `#[test]` +functions and doctests. To make this possible, the return type of these functions are generalized from `()` to a new trait, provisionally called -`Termination`. libstd implements this trait for `!`, `()`, `bool`, -`Error`, `Result where T: Termination, E: Termination`, -and possibly other types TBD. Applications can provide impls -themselves if they want. +`Termination`. libstd implements this trait for `!`, `()`, `Error`, +`Result where T: Termination, E: Termination`, and possibly +other types TBD. Applications can provide impls themselves if they +want. -There is no magic added to function signatures. If you want to use -`?` in either `main` or a `#[test]` you have to write `-> Result<(), -ErrorT>` (or whatever) yourself. (TBD: It may make sense to provide a -type alias like - - type MayFail = Result<(), Box>; - -which would mean less boilerplate in the most common case for `main`.) +There is no magic added to function signatures in rustc. If you want +to use `?` in either `main` or a `#[test]` function you have to write +`-> Result<(), ErrorT>` (or whatever) yourself. However, the rustdoc +template for doctests that are just a function body will be adjusted, +so that `?` can be used without having to write a function head yourself. [Pre-RFC discussion][pre-rfc]. [Prior RFC issue][old-issue]. @@ -41,7 +39,7 @@ programs, designed to be invoked from the Unix shell or a similar environment, have to contain extra boilerplate in order to comply with those environments' conventions. A typical construction is -```rust +``` rust fn inner_main() -> Result<(), ErrorT> { // ... stuff which may fail ... Ok(()) @@ -76,11 +74,11 @@ The design goals for this new feature are, in decreasing order of importance: 1. The `?` operator should be usable in `main` (and `#[test]` - functions). This entails these functions now returning a richer - value than `()`. + functions and doctests). This entails these functions now + returning a richer value than `()`. 1. Existing code with `fn main() -> ()` should not break. 1. Errors returned from `main` in a hosted environment should - **not** trigger a panic, consistent with the general language + *not* trigger a panic, consistent with the general language principle that panics are only for bugs. 1. We should take this opportunity to increase consistency with platform conventions for process termination. These often include @@ -94,20 +92,12 @@ importance: around. Goal 1 dictates that the new return type for `main` will be -`Result` for some T and E. - -To minimize the necessary changes to existing code that wants to start -using `?` in `main`, T should be _allowed_ to be `()`, but other types -in that position may also make sense. For instance, many -implementations of the [`grep`][grep] utility exit with status 0 when -matches are found, 1 when no matches are found, and 2 when I/O errors -occurred; it would be natural to express that using `Result`. - -The boilerplate shown [above][motivation] will work for any E that is -`Display`, but a tighter bound (e.g. `Error` or `Box`) might -make implementation more convenient and/or facilitate better error -messages. +`Result` for some T and E. To minimize the necessary changes to +existing code that wants to start using `?` in `main`, T should be +_allowed_ to be `()`, but other types in that position may also make +sense. The boilerplate shown [above][motivation] will work for any E +that is `Display`, but that is probably too general for the stdlib; we +propose only `Error` types should work by default. [grep]: http://man7.org/linux/man-pages/man1/grep.1.html @@ -116,24 +106,23 @@ messages. When `main` returns a nontrivial value, the runtime needs to know two things about it: what error message, if any, to print, and what value -to pass to the platform's equivalent of [`exit(3)`][exit]. These are -naturally encapsulated in a trait, which we are tentatively calling -`Termination`, with this signature: +to pass to `std::process::exit`. These are naturally encapsulated in +a trait, which we are tentatively calling `Termination`, with this +signature: -```rust +``` rust trait Termination { fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> (); fn exit_status(&self) -> i32; } ``` -The canonical home of this trait is `std::process`. - `write_diagnostic` shall write a complete diagnostic message for `self` to its `stream` argument. If it produces no output, there will -be no output. `stream` will normally be the same stream that `panic` -messages from the main thread would go to, which is normally "standard -error". +be no output. `stream` will be the same stream that `panic` messages +from the main thread would go to, which is normally "standard error". +(An application-controllable override mechanism may make sense, +[see below][squelching-diagnostics].) The `progname` argument is a short version of the program's name (abstractly, what you would type at the command line to start the @@ -151,25 +140,97 @@ library's `exit` primitive), but (as already documented for `process::exit`) on "most Unix-like" operating systems, only the low 8 bits of this value are significant. -(It would probably be a good idea to reexport `libc::EXIT_SUCCESS` and -`libc::EXIT_FAILURE` from `std::process`.) +[std::process::exit]: https://doc.rust-lang.org/std/process/fn.exit.html -Embedded operating systems that have no notion of process exit status -shall ignore the argument to `process::exit`, but shall still call -`write_diagnostic`. Embedded operating systems where returning from -`main` constitutes a _bug_ shall not provide `Termination` at all; -when targeting such systems, the compiler shall insist that the -signature of `main` be `() -> !` instead of `() -> ()`, and `?` shall -continue to be unusable in `main`. +## Changes to `main` +[changes-to-main]: #changes-to-main -[std::process::exit]: https://doc.rust-lang.org/std/process/fn.exit.html +From the perspective of the code that calls `main`, its signature is +now generic: + +``` rust +fn main() -> T { ... } +``` + +It is critical that people _writing_ main should not have to treat it +as a generic, though. Existing code with `fn main() -> ()` should +continue to compile, and code that wants to use the new feature should +be able to write `fn main() -> TermT` for some concrete type `TermT`. + +I also don't know whether the code that calls `main` can accept a +generic. The `lang_start` glue currently receives an unsafe pointer +to `main`, and expects it to have the signature `() -> ()`. The +abstractly correct new signature is `() -> Termination` but I suspect +that this is currently not possible, pending `impl Trait` return types +or something similar. `() -> Box` probably _is_ possible +but requires a heap allocation, which is undesirable in no-std +contexts. + +A solution to both problems is to implement the new semantics entirely +within the compiler. When it notices that `main` returns anything +other than `()`, it renames the function and injects a shim: + +``` rust +fn main() -> () { + let term = $real_main(); + term.write_diagnostic(std::env::progname(), std::io::LOCAL_STDERR); + std::process::exit(term.exit_status()); +} +``` + +## `main` in nostd environments +[main-in-nostd-environments]: #main-in-nostd-environments + +Some no-std environments do have a notion of processes that run and +then exit, but they may or may not have notions of "exit status" or +"error messages". In this case, the signature of `main` should be +unchanged, and the shim should simply ignore whichever aspects of +`Termination` don't make sense in context. + +There are also environments where returning from `main` constitutes a +_bug_. If you are implementing an operating system kernel, for +instance, there may be nothing to return to. Then you want it to be a +compile-time error for `main` to return anything other than `!`. If +everything is implemented correctly, such environments should be able +to get that effect by omitting all stock impls of `Termination` other +than for `!`. Perhaps there should also be a compiler hook that +allows such environments to refuse to let you impl Termination +yourself. + +## Test functions and doctests +[test-functions-and-doctests]: #test-functions-and-doctests + +The harness for `#[test]` functions is very simple; I think it would +be enough to just give `#[test]` functions the same shim that we give +to `main`. The programmer would be responsible for adjusting their +`#[test]` functions' return types if they want to use `?`, but +existing code would continue to work. + +Doctests require a little magic, because you normally don't write the +function head for a doctest yourself, only its body. This magic +belongs in rustdoc, not in rustc. When `maketest` sees that it needs +to insert a function head for `main`, it should now write out + +``` rust +fn main () -> Result<(), ErrorT> { + ... + Ok(()) +} +``` + +for some value of `ErrorT` TBD. It doesn't need to parse the body of +the test to know whether it should do this; it can just do it +unconditionally. ## Standard impls of Termination [standard-impls-of-termination]: #standard-impls-of-termination -At least the following implementations of Termination are available in libstd: +At least the following implementations of Termination are available in +libstd. I use the ISO C constants `EXIT_SUCCESS` and `EXIT_FAILURE` +for exposition. [See below][unix-specific-refinements] for more +discussion of these constants and the `TermStatus` type. -```rust +``` rust impl Termination for ! { fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () { unreachable!(); } @@ -184,11 +245,12 @@ impl Termination for () { { EXIT_SUCCESS } } -impl Termination for bool { +impl Termination for TermStatus { fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () { } fn exit_status(&self) -> i32 - { if *self { EXIT_SUCCESS } else { EXIT_FAILURE } } + { self.0 } +} impl Termination for E { fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () @@ -207,63 +269,134 @@ impl Termination for E { impl Termination for Result { fn write_diagnostic(&self, progname: &str, stream: &mut Write) { match *self { - Ok(ref ok) { ok.write_diagnostic(progname, stream); } + Ok(ref ok) { ok.write_diagnostic(progname, stream); }, Err(ref err) { err.write_diagnostic(progname, stream); } } } fn exit_status(&self) -> i32 { match *self { - Ok(ref ok) { ok.exit_status() } - Err(ref err) { err.exit_status() } + Ok(ref ok) => ok.exit_status(), + Err(ref err) => match err.exit_status() { + 0 | EXIT_SUCCESS => EXIT_FAILURE, + e => e + } } } } ``` -If the platform permits, the actual exit status used for `false` will -be different from the exit status used for an `Error`. For instance, -on Unix-like platforms `false` is mapped to status 1 and `Error`s are -mapped to status 2. (This is the convention used by many -implementations of `grep`, allowing it to distinguish "no error but no -matches found" from "an I/O error occurred".) - -(If we wanted to gild the lily, we could map `io::Error`s to specific -codes from [`sysexits.h`][sysexits], but I don't know that we should -bother, given how few other programs do.) +The impl for `!` allows programs that intend to run forever to be more +self-documenting: `fn main() -> !` will satisfy the implicit trait +bound on the return type. It might not be necessary to have the code +in libstd, if the compiler can figure out for itself that `-> !` +satisfies _any_ return type; I have heard that this works in Haskell. +But it's probably good to have it in the reference manual anyway, so +people know they can do that. + +The impl for `Result` does not allow its `Err` case to produce a +successful exit status. The technical case for doing this is that it +means you can use `Result<(),()>` to encode success or failure without +printing any messages in either case, and the ergonomics case is that +it's less surprising this way. I don't _think_ it gets in the way of +anything a realistic program would want to do. Additional impls of Termination should be added as ergonomics dictate. However, there probably _shouldn't_ be an impl of Termination for Option, because there are equally strong arguments for None indicating -success and None indicating failure. And there probably shouldn't be -an impl for i32 or i8 either, because that would permit the programmer -to return arbitrary numbers from `main` without thinking at all about -whether they make sense as exit statuses. - -[exit]: https://linux.die.net/man/3/exit -[sysexits]: http://www.unix.com/man-pages.php?os=freebsd§ion=3&query=sysexits - -## Implementation issues -[implementation-issues]: #implementation-issues - -The tricky part of implementation is impedance matching between `main` -and `lang_start`. `lang_start` currently receives an unsafe pointer -to `main` and expects it to have the signature `() -> ()`. The -abstractly correct new signature is `() -> &Termination` but I suspect -that this is currently not possible, pending `impl Trait` return types -or something similar. +success and None indicating failure. `bool` is similarly ambiguous. +And there probably shouldn't be an impl for `i32` or `u8` either, +because that would permit the programmer to return arbitrary numbers +from `main` without thinking at all about whether they make sense as +exit statuses. + +A generic implementation of Termination for anything that is Display +_could_ make sense, but my current opinion is that it is too general +and would make it too easy to get undesired behavior by accident. + +## Squelching diagnostics +[squelching-diagnostics]: #squelching-diagnostics + +It is fairly common for command-line tools to have a mode, often +triggered by an option `-q` or `--quiet`, which suppresses all output, +*including error messages*. They still exit unsuccessfully if there +are errors, but they don't print anything at all. This is for use in +shell control-flow constructs, e.g. `if grep -q blah ...; then ...` +An easy way to facilitate this would be to stabilize a subset of the +`set_panic` feature, say a new function `squelch_errors` or +`silence_stderr` which simply discards all output sent to stderr. + +Programs that need to do something more complicated than that are +probably better off printing diagnostics by hand, as is done now. + +## Unix-specific refinements +[unix-specific-refinements]: #unix-specific-refinements + +The C standard only specifies `0`, `EXIT_SUCCESS` and `EXIT_FAILURE` +as arguments to the [`exit`][exit.3] primitive. (`EXIT_SUCCESS` is +not guaranteed to have the value 0, but calling `exit(0)` *is* +guaranteed to have the same effect as calling `exit(EXIT_SUCCESS)`; +several versions of the `exit` manpage are incorrect on this point.) +Any other argument has an implementation-defined effect. + +Within the Unix ecosystem, `exit` is relied upon to pass values in the +range 0 through 127 (*not* 255) up to the parent process. There is no +general agreement on the meaning of specific nonzero exit codes, but +there are many contexts that give specific codes a meaning, such as: + +* POSIX reserves status 127 for certain internal failures in `system` + and `posix_spawn` that cannot practically be reported via `errno`. + +* [`grep`][grep.1] is specified to exit with status 0 if it found a + match, 1 if it found no matches, and an unspecified value greater + than 1 if an error occurred. + +* [Automake's support for basic testing][automake-tests] defines status + 77 to mean "test skipped" and status 99 to mean "hard error" + (I'm not sure precisely what "hard error" is for, but probably + something like "an error so severe that the entire test run should + be abandoned"). + +* The BSDs have, for a long time, provided a header + [`sysexits.h`][sysexits] that defines a fairly rich set of exit + codes for general use, but as far as I know these have never seen + wide adoption. Of these, `EX_USAGE` (code 64) for command-line + syntax errors is probably the most useful. + +* Rust itself uses status 101 for `panic!`, although arguably it + should be calling `abort` instead. (`abort` on Unix systems sends a + different status to the parent than any value you can pass to `exit`.) + +It is unnecessary to put most of this stuff into the Rust stdlib, +especially as some of these conventions contradict each other. +However, the stdlib should not get in the way of a program that +intends to conform to any of the above. This can be done with the +following refinements: + +* The stdlib provides `EXIT_SUCCESS` and `EXIT_FAILURE` constants in + `std::process` (since `exit` is already there). These constants do + _not_ necessarily have the values that the platform's C library + gives them. `EXIT_SUCCESS` is always 0. `EXIT_FAILURE` has the same + value as the C library gives it, _unless_ the C library gives it the + value 1, in which case 2 is used instead. + +* All of the impls of `Termination` in the stdlib are guaranteed to + use only `EXIT_SUCCESS` and `EXIT_FAILURE`, with one exception: + +* There is a type, provisionally called `TermStatus`, which is a + newtype over `i32`; on Unix (but not on Windows), creating one from + a value outside the range 0 ... 255 will panic. It implements + `Termination`, passing its value to `exit` and not printing any + diagnostics. Using this type, you can generate specific exit codes + when appropriate, without having to avoid using `?` in `main`. + + (It can't be called `ExitStatus` because that name is already + taken for the return type of `std::process::Command::status`.) + +[exit.3]: http://www.cplusplus.com/reference/cstdlib/exit/ +[grep.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html +[automake-tests]: https://www.gnu.org/software/automake/manual/html_node/Scripts_002dbased-Testsuites.html +[sysexits]: https://www.freebsd.org/cgi/man.cgi?query=sysexits -Failing that, we _could_ implement the new semantics entirely within -the compiler. When it noticed that `main` returns anything other than -`()`, it would rename the function `inner_main` and inject a shim: - - fn main() -> () { - let term = inner_main(); - term.write_diagnostic(std::env::progname(), std::io::stderr()); - std::process::exit(term.exit_status()); - } - -Similarly for `#[test]` functions. Everything else would then be -straightforward additions to libstd and/or libcore. # How We Teach This [how-we-teach-this]: #how-we-teach-this @@ -272,7 +405,7 @@ This should be taught alongside the `?` operator and error handling in general. The stock `Termination` impls in libstd mean that simple programs that can fail don't need to do anything special: -```rust +``` rust fn main() -> Result<(), io::Error> { let mut stdin = io::stdin(); let mut raw_stdout = io::stdout(); @@ -285,6 +418,9 @@ fn main() -> Result<(), io::Error> { } ``` +Doctest examples can freely use `?` with no extra boilerplate; +`#[test]` examples may need their boilerplate adjusted. + More complex programs, with their own error types, still get platform-consistent exit status behavior for free, as long as they implement `Error`. The Book should describe `Termination` to explain @@ -296,6 +432,10 @@ Tutorial examples should probably still begin with `fn main() -> ()` until the tutorial gets to the point where it starts explaining why `panic!` and `unwrap` are not for "normal errors". +Discussion of `TermStatus` should be reserved for an advanced-topics +section talking about interoperation with the Unix command-line +ecosystem. + # Drawbacks [drawbacks]: #drawbacks @@ -314,8 +454,7 @@ using `panic!` to report ordinary errors because it's less hassle. The [pre-RFC][pre-rfc] included a suggestion to use `catch` instead, but this still involves extra boilerplate in `main` so I'm not -enthusiastic about it. Also, `catch` doesn't seem to be happening -anytime soon. +enthusiastic about it. # Unresolved Questions [unresolved]: #unresolved-questions @@ -330,15 +469,25 @@ can't think of any right now. (Thread join was mentioned in the [pre-RFC][pre-rfc], but that can already relay anything that's `Send`, so I don't see that it adds value there.) +We also need to decide where the trait should live. One obvious place +is in `std::process`, because that is where `exit(i32)` is; on the +other hand, this is basic enough that it may make sense to put at +least some of it in libcore. + I don't know what impls of `Termination` should be available beyond the ones listed above, nor do I know what impls should be in libcore. Most importantly, I do not know whether it is necessary to impl Termination for `Box where T: Termination`. It might be that Box's existing impl of Deref renders this unnecessary. -The `MayFail` type alias seems helpful, but also maybe a little too -single-purpose. And if we ever get return type deduction, that would -completely supersede it, but we'd have burned the name forever. +It may make sense to provide a type alias like + + type MayFail = Result<(), Box>; + +which would mean less boilerplate in the most common case for `main`. +However, this may be too single-purpose for a global-prelude type +alias, and if we ever get return type deduction, that would completely +supersede it, but we'd have burned the name forever. There is an outstanding proposal to [generalize `?`][try-trait] (see also RFC issues [#1718][rfc-i1718] and [#1859][rfc-i1859]); I From 2e76c96f5e10b4eb5248bc33bd9deeed25d8abc2 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Wed, 1 Mar 2017 13:14:55 -0500 Subject: [PATCH 3/9] Link to the 'divergent main' thread --- text/0000-ques-in-main.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/text/0000-ques-in-main.md b/text/0000-ques-in-main.md index 24d3e919398..ca5c203d92e 100644 --- a/text/0000-ques-in-main.md +++ b/text/0000-ques-in-main.md @@ -187,15 +187,17 @@ then exit, but they may or may not have notions of "exit status" or unchanged, and the shim should simply ignore whichever aspects of `Termination` don't make sense in context. -There are also environments where returning from `main` constitutes a -_bug_. If you are implementing an operating system kernel, for -instance, there may be nothing to return to. Then you want it to be a -compile-time error for `main` to return anything other than `!`. If -everything is implemented correctly, such environments should be able -to get that effect by omitting all stock impls of `Termination` other -than for `!`. Perhaps there should also be a compiler hook that -allows such environments to refuse to let you impl Termination -yourself. +There are also environments where +[returning from `main` constitutes a _bug_.][divergent-main] If you +are implementing an operating system kernel, for instance, there may +be nothing to return to. Then you want it to be a compile-time error +for `main` to return anything other than `!`. If everything is +implemented correctly, such environments should be able to get that +effect by omitting all stock impls of `Termination` other than for +`!`. Perhaps there should also be a compiler hook that allows such +environments to refuse to let you impl Termination yourself. + +[divergent-main]: https://internals.rust-lang.org/t/allowing-for-main-to-be-divergent-in-embedded-environments/4717 ## Test functions and doctests [test-functions-and-doctests]: #test-functions-and-doctests From 9e0c0f641eaaa88a8c6dd593407a1596369635bf Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Mon, 15 May 2017 16:54:41 -0400 Subject: [PATCH 4/9] Major revision with simpler Termination trait. --- text/0000-ques-in-main.md | 631 +++++++++++++++++++++++++------------- 1 file changed, 412 insertions(+), 219 deletions(-) diff --git a/text/0000-ques-in-main.md b/text/0000-ques-in-main.md index ca5c203d92e..1c14e1d74c2 100644 --- a/text/0000-ques-in-main.md +++ b/text/0000-ques-in-main.md @@ -11,8 +11,8 @@ functions and doctests. To make this possible, the return type of these functions are generalized from `()` to a new trait, provisionally called -`Termination`. libstd implements this trait for `!`, `()`, `Error`, -`Result where T: Termination, E: Termination`, and possibly +`Termination`. libstd implements this trait for `!`, `()`, +`process::ExitStatus`, `Result<(), E> where E: Error`, and possibly other types TBD. Applications can provide impls themselves if they want. @@ -33,6 +33,109 @@ so that `?` can be used without having to write a function head yourself. It is currently not possible to use `?` in `main`, because `main`'s return type is required to be `()`. This is a trip hazard for new users of the language, and complicates "programming in the small". +For example, consider a version of the +[CSV-parsing example from the Rust Book][csv-example] +(I have omitted a chunk of command-line parsing code and the +definition of the Row type, to keep it short): + +``` rust +fn main() { + let argv = env::args(); + let _ = argv.next(); + let data_path = argv.next().unwrap(); + let city = argv.next().unwrap(); + + let file = File::open(data_path).unwrap(); + let mut rdr = csv::Reader::from_reader(file); + + for row in rdr.decode::() { + let row = row.unwrap(); + + if row.city == city { + println!("{}, {}: {:?}", + row.city, row.country, + row.population.expect("population count")); + } + } +} +``` + +The Rust Book uses this as a starting point for a demonstration of how +to do error handing _properly_, i.e. without using `unwrap` and +`expect`. But suppose this is a program for your own personal use. +You are only writing it in Rust because it needs to crunch an enormous +data file and high-level scripting languages are too slow. You don't +especially _care_ about proper error handling, you just want something +that works, with minimal programming effort. You'd like to not have +to remember that this is `main` and you can't use `?`. You would like +to write instead + +``` rust +fn main() -> Result<(), Box> { + let argv = env::args(); + let _ = argv.next(); + let data_path = argv.next()?; + let city = argv.next()?; + + let file = File::open(data_path)?; + let mut rdr = csv::Reader::from_reader(file); + + for row in rdr.decode::() { + let row = row?; + + if row.city == city { + println!("{}, {}: {:?}", + row.city, row.country, row.population?); + } + } + Ok(()) +} +``` + +(Just to be completely clear, this is not intended to _reduce_ the +amount of error-handling boilerplate one has to write; only to make it +be the same in `main` as it would be for any other function.) + +For the same reason, it is not possible to use `?` in doctests and +`#[test]` functions. This is only an inconvenience for `#[test]` +functions, same as for `main`, but it's a major problem for doctests, +because doctests are supposed to demonstrate normal usage, as well as +testing functionality. Taking an +[example from the stdlib][to-socket-addrs]: + +``` rust +use std::net::{SocketAddrV4, TcpStream, UdpSocket, TcpListener, Ipv4Addr}; +let ip = Ipv4Addr::new(127, 0, 0, 1); +let port = 12345; + +// The following lines are equivalent modulo possible "localhost" name +// resolution differences +let tcp_s = TcpStream::connect(SocketAddrV4::new(ip, port)); +let tcp_s = TcpStream::connect((ip, port)); +let tcp_s = TcpStream::connect(("127.0.0.1", port)); +let tcp_s = TcpStream::connect(("localhost", port)); +let tcp_s = TcpStream::connect("127.0.0.1:12345"); +let tcp_s = TcpStream::connect("localhost:12345"); + +// TcpListener::bind(), UdpSocket::bind() and UdpSocket::send_to() +// behave similarly +let tcp_l = TcpListener::bind("localhost:12345"); + +let mut udp_s = UdpSocket::bind(("127.0.0.1", port)).unwrap(); // XXX +udp_s.send_to(&[7], (ip, 23451)).unwrap(); // XXX +``` + +The lines marked `XXX` have to use `unwrap`, because a doctest is the +body of a `main` function, but in normal usage, they would be written + +``` rust +let mut udp_s = UdpSocket::bind(("127.0.0.1", port))?; +udp_s.send_to(&[7], (ip, 23451))?; +``` + +and that's what the documentation _ought_ to say. Documentation +writers can work around this by including their own `main` as +hidden code, but they shouldn't have to. On a related note, `main` returning `()` means that short-lived programs, designed to be invoked from the Unix shell or a similar @@ -47,7 +150,6 @@ fn inner_main() -> Result<(), ErrorT> { fn main() -> () { use std::process::exit; - use std::io::{Write,stderr}; use libc::{EXIT_SUCCESS, EXIT_FAILURE}; exit(match inner_main() { @@ -55,7 +157,7 @@ fn main() -> () { Err(ref err) => { let progname = get_program_name(); - writeln!(stderr(), "{}: {}\n", progname, err); + eprintln!("{}: {}\n", progname, err); EXIT_FAILURE } @@ -63,9 +165,11 @@ fn main() -> () { } ``` -Both of these problems can be solved at once if the compiler and/or -libstd are taught to recognize a `main` that returns -`Result<(), ErrorT>` and supply boilerplate similar to the above. +These problems can be solved by generalizing the return type of `main` +and test functions. + +[csv-example]: https://doc.rust-lang.org/book/error-handling.html#case-study-a-program-to-read-population-data +[to-socket-addrs]: https://doc.rust-lang.org/std/net/trait.ToSocketAddrs.html # Detailed design [design]: #detailed-design @@ -73,9 +177,9 @@ libstd are taught to recognize a `main` that returns The design goals for this new feature are, in decreasing order of importance: -1. The `?` operator should be usable in `main` (and `#[test]` - functions and doctests). This entails these functions now - returning a richer value than `()`. +1. The `?` operator should be usable in `main`, `#[test]` functions, + and doctests. This entails these functions now returning a richer + value than `()`. 1. Existing code with `fn main() -> ()` should not break. 1. Errors returned from `main` in a hosted environment should *not* trigger a panic, consistent with the general language @@ -92,14 +196,13 @@ importance: around. Goal 1 dictates that the new return type for `main` will be -`Result` for some T and E. To minimize the necessary changes to +`Result` for some T and E. To minimize the necessary changes to existing code that wants to start using `?` in `main`, T should be -_allowed_ to be `()`, but other types in that position may also make -sense. The boilerplate shown [above][motivation] will work for any E -that is `Display`, but that is probably too general for the stdlib; we -propose only `Error` types should work by default. - -[grep]: http://man7.org/linux/man-pages/man1/grep.1.html +allowed to be `()`, but other types in that position may also make +sense. The appropriate bound for E is unclear; there are plausible +arguments for at least `Error`, `Debug`, and `Display`. This proposal +starts from the narrowest possibility and provides for only +`Result<() E> where E: Error`. ## The `Termination` trait [the-termination-trait]: #the-termination-trait @@ -112,69 +215,171 @@ signature: ``` rust trait Termination { - fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> (); - fn exit_status(&self) -> i32; + fn report(self) -> i32; } ``` -`write_diagnostic` shall write a complete diagnostic message for -`self` to its `stream` argument. If it produces no output, there will -be no output. `stream` will be the same stream that `panic` messages -from the main thread would go to, which is normally "standard error". -(An application-controllable override mechanism may make sense, -[see below][squelching-diagnostics].) - -The `progname` argument is a short version of the program's name -(abstractly, what you would type at the command line to start the -program, assuming it were in `PATH`; concretely, the basename of -`argv[0]`, with any trailing `.exe` or `.com` chopped off on Windows, -but not on other platforms). This makes it easy to generate -diagnostics in the style conventional for "Unixy" systems, e.g. - - grep: /etc/shadow: Permission denied - -`exit_status` shall convert `self` to a platform-specific exit code, -conveying at least a notion of success or failure. The return type is -`i32` to match [std::process::exit][] (which probably calls the C -library's `exit` primitive), but (as already documented for -`process::exit`) on "most Unix-like" operating systems, only the low 8 -bits of this value are significant. +`report` is a call-once function; it consumes self. The runtime +guarantees to call this function after `main` returns, but at a point +where it is still safe to use `eprintln!` or `io::stderr()` to print +error messages. `report` is not _required_ to print error messages, +and if it doesn't, nothing will be printed. The value it returns will +be passed to `std::process::exit`, and shall convey at least a notion +of success or failure. The return type is `i32` to match +[std::process::exit][] (which probably calls the C library's `exit` +primitive), but (as already documented for `process::exit`) on "most +Unix-like" operating systems, only the low 8 bits of this value are +significant. [std::process::exit]: https://doc.rust-lang.org/std/process/fn.exit.html -## Changes to `main` -[changes-to-main]: #changes-to-main +## Standard impls of Termination +[standard-impls-of-termination]: #standard-impls-of-termination -From the perspective of the code that calls `main`, its signature is -now generic: +At least the following implementations of Termination are available in +libstd. I use the ISO C constants `EXIT_SUCCESS` and `EXIT_FAILURE` +for exposition; they are not necesssarily intended to be the exact +values passed to `process::exit`. ``` rust -fn main() -> T { ... } +impl Termination for ! { + fn report(self) -> i32 { unreachable!(); } +} + +impl Termination for () { + fn report(self) -> i32 { EXIT_SUCCESS } +} + +impl Termination for std::process::ExitStatus { + fn report(self) -> i32 { + self.code().expect("Cannot use a signal ExitStatus to exit") + } +} + +fn print_diagnostics_for_error(err: &E) { + // unspecified, but along the lines of: + if let Some(ref cause) = err.cause() { + print_diagnostics_for_error(cause); + } + eprintln!("{}: {}", get_program_name(), err.description()); +} + +impl Termination for Result<(), E> { + fn report(self) -> i32 { + match self { + Ok(_) => EXIT_SUCCESS, + Err(ref err) => { + print_diagnostics_for_error(err); + EXIT_FAILURE + } + } + } +} ``` -It is critical that people _writing_ main should not have to treat it -as a generic, though. Existing code with `fn main() -> ()` should -continue to compile, and code that wants to use the new feature should -be able to write `fn main() -> TermT` for some concrete type `TermT`. +The impl for `!` allows programs that intend to run forever to be more +self-documenting: `fn main() -> !` will satisfy the implicit trait +bound on the return type. It might not be necessary to have code for +this impl in libstd, since `-> !` satisfies `-> ()`, but it should +appear in the reference manual anyway, so people know they can do +that, and it may also be desirable as a backstop against a `main` that +does somehow return, despite declaring that it doesn't. + +The impl for `ExitStatus` allows programs to generate both success and +failure conditions _without_ any errors printed, by returning from +`main`. This is meant to be used by sophisticated programs that do +all of their own error-message printing themselves. +[See below][exit-status] for more discussion and related changes to +`ExitStatus`. -I also don't know whether the code that calls `main` can accept a -generic. The `lang_start` glue currently receives an unsafe pointer -to `main`, and expects it to have the signature `() -> ()`. The -abstractly correct new signature is `() -> Termination` but I suspect -that this is currently not possible, pending `impl Trait` return types -or something similar. `() -> Box` probably _is_ possible -but requires a heap allocation, which is undesirable in no-std -contexts. +Additional impls of Termination should be added as ergonomics dictate. +For instance, it may well make sense to provide an impl for +`Result<(), E> where E: Display` or `... where E: Debug`, because +programs may find it convenient to use bare strings as error values, +and application error types are not obliged to be `std::Error`s. +Also, it is unclear to me whether `impl Termination for +Result<(), E>` applies to `Result<(), Box>`. If it doesn't, we +will almost certainly need to add `impl> Termination for +Result<(), E>`. I hope that isn't necessary, because then we might +also need `Rc` and `Arc` and `Cow` and +`RefCell` and ... + +There probably _shouldn't_ be an impl of Termination for Option, +because there are equally strong arguments for None indicating success +and None indicating failure. `bool` is similarly ambiguous. And +there probably shouldn't be an impl for `i32` or `u8` either, because +that would permit the programmer to return arbitrary numbers from +`main` without thinking at all about whether they make sense as exit +statuses. + +A previous revision of this RFC included an impl for `Result +where T: Termination, E: Termination`, but it has been removed from +this version, as some people felt it would allow undesirable behavior. +It can always be added again in the future. + +## Changes to `lang_start` +[changes-to-lang-start]: #changes-to-lang-start + +The `start` "lang item", the function that calls `main`, takes the +address of `main` as an argument. Its signature is currently -A solution to both problems is to implement the new semantics entirely -within the compiler. When it notices that `main` returns anything -other than `()`, it renames the function and injects a shim: +``` rust +#[lang = "start"] +fn lang_start(main: *const u8, argc: isize, argv: *const *const u8) -> isize +``` + +It will need to become generic, something like ``` rust -fn main() -> () { - let term = $real_main(); - term.write_diagnostic(std::env::progname(), std::io::LOCAL_STDERR); - std::process::exit(term.exit_status()); +#[lang = "start"] +fn lang_start + (main: fn() -> T, argc: isize, argv: *const *const u8) -> ! +``` + +(Note: the current `isize` return type is incorrect. As is, the +correct return type is `libc::c_int`. We can avoid the entire issue by +requiring `lang_start` to call `process::exit` or equivalent itself; +this also moves one step toward not depending on the C runtime.) + +The implementation for typical "hosted" environments will be something +like + +``` rust +#[lang = "start"] +fn lang_start + (main: fn() -> T, argc: isize, argv: *const *const u8) -> ! +{ + use panic; + use sys; + use sys_common; + use sys_common::thread_info; + use thread::Thread; + use process::exit; + + sys::init(); + + 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 status = match panic::catch_unwind(main) { + Ok(term) { term.report() } + Err(_) { 101 } + } + sys_common::cleanup(); + status + }); } ``` @@ -182,10 +387,12 @@ fn main() -> () { [main-in-nostd-environments]: #main-in-nostd-environments Some no-std environments do have a notion of processes that run and -then exit, but they may or may not have notions of "exit status" or -"error messages". In this case, the signature of `main` should be -unchanged, and the shim should simply ignore whichever aspects of -`Termination` don't make sense in context. +then exit, but do not have a notion of "exit status". In this case, +`process::exit` probably already ignores its argument, so `main` and +the `start` lang item do not need to change. Similarly, in an +environment where there is no such thing as an "error message", +`io::stderr()` probably already points to the bit bucket, so `report` +functions can go ahead and use `eprintln!` anyway. There are also environments where [returning from `main` constitutes a _bug_.][divergent-main] If you @@ -202,16 +409,13 @@ environments to refuse to let you impl Termination yourself. ## Test functions and doctests [test-functions-and-doctests]: #test-functions-and-doctests -The harness for `#[test]` functions is very simple; I think it would -be enough to just give `#[test]` functions the same shim that we give -to `main`. The programmer would be responsible for adjusting their -`#[test]` functions' return types if they want to use `?`, but -existing code would continue to work. +The harness for `#[test]` functions will need to be changed, similarly +to how the "start" lang item was changed. Tests which return +anything whose `report()` method returns a nonzero value should be +considered to have failed. -Doctests require a little magic, because you normally don't write the -function head for a doctest yourself, only its body. This magic -belongs in rustdoc, not in rustc. When `maketest` sees that it needs -to insert a function head for `main`, it should now write out +Doctests require a little magic in rustdoc: when `maketest` sees that +it needs to insert a function head for `main`, it should now write out ``` rust fn main () -> Result<(), ErrorT> { @@ -224,111 +428,39 @@ for some value of `ErrorT` TBD. It doesn't need to parse the body of the test to know whether it should do this; it can just do it unconditionally. -## Standard impls of Termination -[standard-impls-of-termination]: #standard-impls-of-termination +## New constructors for `ExitStatus` +[exit-status]: #exit-status -At least the following implementations of Termination are available in -libstd. I use the ISO C constants `EXIT_SUCCESS` and `EXIT_FAILURE` -for exposition. [See below][unix-specific-refinements] for more -discussion of these constants and the `TermStatus` type. +As mentioned above, we propose to reuse `process::ExitStatus` as a way +for a program to generate both success and failure conditions +_without_ any errors printed, by returning it from `main`. To make +this more convenient, we also propose to add the following new +constructors to `ExitStatus`: ``` rust -impl Termination for ! { - fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () - { unreachable!(); } - fn exit_status(&self) -> i32 - { unreachable!(); } -} - -impl Termination for () { - fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () - { } - fn exit_status(&self) -> i32 - { EXIT_SUCCESS } -} - -impl Termination for TermStatus { - fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () - { } - fn exit_status(&self) -> i32 - { self.0 } -} - -impl Termination for E { - fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> () - { - // unspecified, but not entirely unlike this: - if let Some(ref cause) = self.cause() { - cause.write_diagnostic(progname, stream); - } - writeln!(stream, "{}: {}\n", progname, self.description()); - } - - fn exit_status(&self) -> i32 - { EXIT_FAILURE } -} - -impl Termination for Result { - fn write_diagnostic(&self, progname: &str, stream: &mut Write) { - match *self { - Ok(ref ok) { ok.write_diagnostic(progname, stream); }, - Err(ref err) { err.write_diagnostic(progname, stream); } - } - } - fn exit_status(&self) -> i32 { - match *self { - Ok(ref ok) => ok.exit_status(), - Err(ref err) => match err.exit_status() { - 0 | EXIT_SUCCESS => EXIT_FAILURE, - e => e - } - } - } +impl ExitStatus { + /// Return an ExitStatus value representing success. + /// (ExitStatus::ok()).success() is guaranteed to be true, and + /// (ExitStatus::ok()).code() is guaranteed to be Some(0). + pub fn ok() -> Self; + + /// Return an ExitStatus value representing failure. + /// (ExitStatus::failure()).success() is guaranteed to be false, and + /// (ExitStatus::failure()).code() is guaranteed to be Some(n), + /// for some unspecified n, 1 < n < 64. + pub fn failure() -> Self; + + /// Return an ExitStatus value representing a specific exit code. + /// The difference between this method and ExitStatusExt::from_raw + /// is that this method can only be used to produce ExitStatus + /// values that will pass unmodified through the operating system + /// primitive "exit" and "wait" operations on all supported + /// platforms. (Conveniently, this is exactly the range of u8.) + pub fn from_code(code: u8) -> Self; } ``` -The impl for `!` allows programs that intend to run forever to be more -self-documenting: `fn main() -> !` will satisfy the implicit trait -bound on the return type. It might not be necessary to have the code -in libstd, if the compiler can figure out for itself that `-> !` -satisfies _any_ return type; I have heard that this works in Haskell. -But it's probably good to have it in the reference manual anyway, so -people know they can do that. - -The impl for `Result` does not allow its `Err` case to produce a -successful exit status. The technical case for doing this is that it -means you can use `Result<(),()>` to encode success or failure without -printing any messages in either case, and the ergonomics case is that -it's less surprising this way. I don't _think_ it gets in the way of -anything a realistic program would want to do. - -Additional impls of Termination should be added as ergonomics dictate. -However, there probably _shouldn't_ be an impl of Termination for -Option, because there are equally strong arguments for None indicating -success and None indicating failure. `bool` is similarly ambiguous. -And there probably shouldn't be an impl for `i32` or `u8` either, -because that would permit the programmer to return arbitrary numbers -from `main` without thinking at all about whether they make sense as -exit statuses. - -A generic implementation of Termination for anything that is Display -_could_ make sense, but my current opinion is that it is too general -and would make it too easy to get undesired behavior by accident. - -## Squelching diagnostics -[squelching-diagnostics]: #squelching-diagnostics - -It is fairly common for command-line tools to have a mode, often -triggered by an option `-q` or `--quiet`, which suppresses all output, -*including error messages*. They still exit unsuccessfully if there -are errors, but they don't print anything at all. This is for use in -shell control-flow constructs, e.g. `if grep -q blah ...; then ...` -An easy way to facilitate this would be to stabilize a subset of the -`set_panic` feature, say a new function `squelch_errors` or -`silence_stderr` which simply discards all output sent to stderr. - -Programs that need to do something more complicated than that are -probably better off printing diagnostics by hand, as is done now. +The first method's name is `ok` because `success` is already taken. ## Unix-specific refinements [unix-specific-refinements]: #unix-specific-refinements @@ -340,10 +472,16 @@ guaranteed to have the same effect as calling `exit(EXIT_SUCCESS)`; several versions of the `exit` manpage are incorrect on this point.) Any other argument has an implementation-defined effect. -Within the Unix ecosystem, `exit` is relied upon to pass values in the -range 0 through 127 (*not* 255) up to the parent process. There is no -general agreement on the meaning of specific nonzero exit codes, but -there are many contexts that give specific codes a meaning, such as: +Within the Unix ecosystem, `exit` can be relied upon to pass values in +the range 0 through 255 up to the parent process; this is the range +proposed for `ExitStatus::from_code`. (POSIX says that one should be +able to pass a full C `int` through `exit` as long as the parent uses +`waitid` to retrieve the value, but this is not widely implemented. +Also, values 128 through 255 have historically been avoided because +older implementations were unreliable about zero- rather than +sign-extending in `WEXITSTATUS`.) There is no general agreement on +the meaning of specific nonzero exit codes, but there are many +contexts that give specific codes a meaning, such as: * POSIX reserves status 127 for certain internal failures in `system` and `posix_spawn` that cannot practically be reported via `errno`. @@ -374,25 +512,15 @@ However, the stdlib should not get in the way of a program that intends to conform to any of the above. This can be done with the following refinements: -* The stdlib provides `EXIT_SUCCESS` and `EXIT_FAILURE` constants in - `std::process` (since `exit` is already there). These constants do - _not_ necessarily have the values that the platform's C library - gives them. `EXIT_SUCCESS` is always 0. `EXIT_FAILURE` has the same - value as the C library gives it, _unless_ the C library gives it the - value 1, in which case 2 is used instead. - -* All of the impls of `Termination` in the stdlib are guaranteed to - use only `EXIT_SUCCESS` and `EXIT_FAILURE`, with one exception: - -* There is a type, provisionally called `TermStatus`, which is a - newtype over `i32`; on Unix (but not on Windows), creating one from - a value outside the range 0 ... 255 will panic. It implements - `Termination`, passing its value to `exit` and not printing any - diagnostics. Using this type, you can generate specific exit codes - when appropriate, without having to avoid using `?` in `main`. +* All of the implementations of `Termination` in the stdlib, except + the one for `ExitStatus` itself, are guaranteed to behave as-if they + use only `ExitStatus::ok()` and `ExitStatus::failure()`. - (It can't be called `ExitStatus` because that name is already - taken for the return type of `std::process::Command::status`.) +* The value used by `ExitStatus::failure()` is guaranteed to be + greater than 1 and less than 64. This avoids collisions with all of + the above conventions. (I recommend we actually use 2, because + people may think that any larger value has a specific meaning, and + then waste time trying to find out what it is.) [exit.3]: http://www.cplusplus.com/reference/cstdlib/exit/ [grep.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html @@ -405,7 +533,7 @@ following refinements: This should be taught alongside the `?` operator and error handling in general. The stock `Termination` impls in libstd mean that simple -programs that can fail don't need to do anything special: +programs that can fail don't need to do anything special. ``` rust fn main() -> Result<(), io::Error> { @@ -420,23 +548,89 @@ fn main() -> Result<(), io::Error> { } ``` -Doctest examples can freely use `?` with no extra boilerplate; -`#[test]` examples may need their boilerplate adjusted. +Programs that care about the exact structure of their error messages +will still need to use `main` primarily for error reporting. +Returning to the [CSV-parsing example][csv-example], a "professional" +version of the program might look something like this (assume all of +the boilerplate involved in the definition of `AppError` is just off +the top of your screen): -More complex programs, with their own error types, still get -platform-consistent exit status behavior for free, as long as they -implement `Error`. The Book should describe `Termination` to explain -_how_ `Result`s returned from `main` turn into error messages and exit -statuses, but as a thing that most programs will not need to deal with -directly. +``` rust +struct Args { + progname: String, + data_path: PathBuf, + city: String +} + +fn parse_args() -> Result { + let argv = env::args_os(); + let progname = argv.next().into_string()?; + let data_path = PathBuf::from(argv.next()); + let city = argv.next().into_string()?; + if let Some(_) = argv.next() { + return Err(UsageError("too many arguments")); + } + Ok(Args { progname, data_path, city }) +} + +fn process(city: &String, data_path: &Path) -> Result { + let file = File::open(args.data_path)?; + let mut rdr = csv::Reader::from_reader(file); + + for row in rdr.decode::() { + let row = row?; + + if row.city == city { + println!("{}, {}: {:?}", + row.city, row.country, row.population?); + } + } +} + +fn main() -> ExitStatus { + match parse_args() { + Err(err) => { + eprintln!("{}", err); + ExitStatus::failure() + }, + Ok(args) => { + match process(&args.city, &args.data_path) { + Err(err) => { + eprintln!("{}: {}: {}", + args.progname, args.data_path, err); + ExitStatus::failure() + }, + Ok(_) => ExitStatus::ok() + } + } + } +} +``` + +and a detailed error-handling tutorial could build that up from the +quick-and-dirty version. Notice that this is not using `?` in main, +but it _is_ using the generalized `main` return value. The +`catch`-block feature (part of [RFC #243][rfc243] along with `?`; +[issue #39849][issue39849]) may well enable shortening this `main` +and/or putting `parse_args` and `process` back inline. Tutorial examples should probably still begin with `fn main() -> ()` until the tutorial gets to the point where it starts explaining why -`panic!` and `unwrap` are not for "normal errors". +`panic!` and `unwrap` are not for "normal errors". The `Termination` +trait should also be explained at that point, to illuminate _how_ +`Result`s returned from `main` turn into error messages and exit +statuses, but as a thing that most programs will not need to deal with +directly. -Discussion of `TermStatus` should be reserved for an advanced-topics -section talking about interoperation with the Unix command-line -ecosystem. +Discussion of `ExitStatus::from_code` should be reserved for an +advanced-topics section talking about interoperation with the Unix +command-line ecosystem. + +Doctest examples can freely use `?` with no extra boilerplate; +`#[test]` examples may need their boilerplate adjusted. + +[rfc243]: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md +[issue39849]: https://github.com/rust-lang/rust/issues/39849 # Drawbacks [drawbacks]: #drawbacks @@ -454,9 +648,10 @@ Do nothing; continue to live with the trip hazard, the extra boilerplate required to comply with platform conventions, and people using `panic!` to report ordinary errors because it's less hassle. -The [pre-RFC][pre-rfc] included a suggestion to use `catch` instead, -but this still involves extra boilerplate in `main` so I'm not -enthusiastic about it. +"Template projects" (e.g. [quickstart][]) mean that one need not write +out all the boilerplate by hand, but it's still there. + +[quickstart]: https://github.com/rusttemplates/quickstart # Unresolved Questions [unresolved]: #unresolved-questions @@ -474,13 +669,11 @@ so I don't see that it adds value there.) We also need to decide where the trait should live. One obvious place is in `std::process`, because that is where `exit(i32)` is; on the other hand, this is basic enough that it may make sense to put at -least some of it in libcore. +least some of it in libcore. Note that the `start` lang item is not +in libcore. I don't know what impls of `Termination` should be available beyond the ones listed above, nor do I know what impls should be in libcore. -Most importantly, I do not know whether it is necessary to impl -Termination for `Box where T: Termination`. It might be that Box's -existing impl of Deref renders this unnecessary. It may make sense to provide a type alias like From ac19f3c91afc7b7075fdb5192ec140f0eb0bcef6 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Thu, 25 May 2017 20:08:47 -0400 Subject: [PATCH 5/9] Fix typo; more thorough discussion of exit code portability --- text/0000-ques-in-main.md | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/text/0000-ques-in-main.md b/text/0000-ques-in-main.md index 1c14e1d74c2..23d43cd7a73 100644 --- a/text/0000-ques-in-main.md +++ b/text/0000-ques-in-main.md @@ -202,7 +202,7 @@ allowed to be `()`, but other types in that position may also make sense. The appropriate bound for E is unclear; there are plausible arguments for at least `Error`, `Debug`, and `Display`. This proposal starts from the narrowest possibility and provides for only -`Result<() E> where E: Error`. +`Result<(), E> where E: Error`. ## The `Termination` trait [the-termination-trait]: #the-termination-trait @@ -474,13 +474,8 @@ Any other argument has an implementation-defined effect. Within the Unix ecosystem, `exit` can be relied upon to pass values in the range 0 through 255 up to the parent process; this is the range -proposed for `ExitStatus::from_code`. (POSIX says that one should be -able to pass a full C `int` through `exit` as long as the parent uses -`waitid` to retrieve the value, but this is not widely implemented. -Also, values 128 through 255 have historically been avoided because -older implementations were unreliable about zero- rather than -sign-extending in `WEXITSTATUS`.) There is no general agreement on -the meaning of specific nonzero exit codes, but there are many +proposed for `ExitStatus::from_code`. There is no general agreement +on the meaning of specific nonzero exit codes, but there are many contexts that give specific codes a meaning, such as: * POSIX reserves status 127 for certain internal failures in `system` @@ -647,7 +642,6 @@ surprising than the status quo. Do nothing; continue to live with the trip hazard, the extra boilerplate required to comply with platform conventions, and people using `panic!` to report ordinary errors because it's less hassle. - "Template projects" (e.g. [quickstart][]) mean that one need not write out all the boilerplate by hand, but it's still there. @@ -691,6 +685,32 @@ sure it doesn't conflict and we should also figure out whether we would need more impls of `Termination` to make them play well together. +Limiting `ExitStatus::from_code` to the range of a `u8` is somewhat +arbitrary. There is an argument for allowing the full range of `i32`, +consistent with `process::exit`. On Windows, arbitrary `DWORD` +quantities can pass through `ExitProcess` to `GetExitCodeProcess`, +except that the value 259 is reserved to mean "this process is still +running." On fully POSIX-compliant systems, it is possible to pass an +arbitrary `int` through `_exit` to `waitid`, but not `waitpid`; +`waitid` is not available on all contemporary Unixes (e.g. NetBSD +doesn't have it) and transmission of arbitrary `int`s is not +implemented by all those that do have it (e.g. Linux and FreeBSD +truncate the exit status to `u8`, same as for `waitpid`, and OSX +truncates it to 24 *signed* bits). The core Rust stdlib generally +refrains from providing features that work on some but not all +supported platforms, so I think the restriction to `u8` is +appropriate. + +There is also an argument for limiting `ExitStatus::from_code` to the +range 0 through 127, because some shells conflate exit codes above 127 +with *signal* statuses (e.g. in Bourne shell, `$?` having the value +130 could mean either exit code 130, or killed by signal 2), and +because some old C libraries would sign- rather than zero-extend in +`WEXITSTATUS`. However, these would not affect Rust programs +communicating with other Rust programs so I do not think the +limitation is appropriate. Also, there is currently no way to express +the range 0 through 127 in the type system. + Most operating systems accept only a scalar exit status, but [Plan 9][], uniquely (to my knowledge), takes an entire string (see [`exits(2)`][exits.2]). Do we care? If we care, what do we do about From 2c4bc963a0a30f47333653f1241c7f0df7f0eba2 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Thu, 25 May 2017 20:12:01 -0400 Subject: [PATCH 6/9] Don't use description() --- text/0000-ques-in-main.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-ques-in-main.md b/text/0000-ques-in-main.md index 23d43cd7a73..1ad4fab57fd 100644 --- a/text/0000-ques-in-main.md +++ b/text/0000-ques-in-main.md @@ -261,7 +261,7 @@ fn print_diagnostics_for_error(err: &E) { if let Some(ref cause) = err.cause() { print_diagnostics_for_error(cause); } - eprintln!("{}: {}", get_program_name(), err.description()); + eprintln!("{}: {}", get_program_name(), err); } impl Termination for Result<(), E> { From f50a6363a988c8291d22f796ab16aebd9c7cf594 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Mon, 26 Jun 2017 12:10:12 -0400 Subject: [PATCH 7/9] Another major revision, dropping ExitStatus and adding deployment plan --- text/0000-ques-in-main.md | 654 +++++++++++++++++++++----------------- 1 file changed, 369 insertions(+), 285 deletions(-) diff --git a/text/0000-ques-in-main.md b/text/0000-ques-in-main.md index 1ad4fab57fd..3c36ffefdaa 100644 --- a/text/0000-ques-in-main.md +++ b/text/0000-ques-in-main.md @@ -11,16 +11,17 @@ functions and doctests. To make this possible, the return type of these functions are generalized from `()` to a new trait, provisionally called -`Termination`. libstd implements this trait for `!`, `()`, -`process::ExitStatus`, `Result<(), E> where E: Error`, and possibly -other types TBD. Applications can provide impls themselves if they -want. +`Termination`. libstd implements this trait for a set of types +partially TBD (see [list below](#standard-impls-of-termination); +applications can provide impls themselves if they want. There is no magic added to function signatures in rustc. If you want to use `?` in either `main` or a `#[test]` function you have to write -`-> Result<(), ErrorT>` (or whatever) yourself. However, the rustdoc -template for doctests that are just a function body will be adjusted, -so that `?` can be used without having to write a function head yourself. +`-> Result<(), ErrorT>` (or whatever) yourself. Initially, it will +also be necessary to write a hidden function head for any doctest that +wants to use `?`, but eventually (see the +[deployment plan](#deployment-plan) below) the default doctest +template will be adjusted to make this unnecessary most of the time. [Pre-RFC discussion][pre-rfc]. [Prior RFC issue][old-issue]. @@ -104,23 +105,8 @@ testing functionality. Taking an [example from the stdlib][to-socket-addrs]: ``` rust -use std::net::{SocketAddrV4, TcpStream, UdpSocket, TcpListener, Ipv4Addr}; -let ip = Ipv4Addr::new(127, 0, 0, 1); +use std::net::UdpSocket; let port = 12345; - -// The following lines are equivalent modulo possible "localhost" name -// resolution differences -let tcp_s = TcpStream::connect(SocketAddrV4::new(ip, port)); -let tcp_s = TcpStream::connect((ip, port)); -let tcp_s = TcpStream::connect(("127.0.0.1", port)); -let tcp_s = TcpStream::connect(("localhost", port)); -let tcp_s = TcpStream::connect("127.0.0.1:12345"); -let tcp_s = TcpStream::connect("localhost:12345"); - -// TcpListener::bind(), UdpSocket::bind() and UdpSocket::send_to() -// behave similarly -let tcp_l = TcpListener::bind("localhost:12345"); - let mut udp_s = UdpSocket::bind(("127.0.0.1", port)).unwrap(); // XXX udp_s.send_to(&[7], (ip, 23451)).unwrap(); // XXX ``` @@ -140,7 +126,12 @@ hidden code, but they shouldn't have to. On a related note, `main` returning `()` means that short-lived programs, designed to be invoked from the Unix shell or a similar environment, have to contain extra boilerplate in order to comply with -those environments' conventions. A typical construction is +those environments' conventions, and must ignore the dire warnings +about destructors not getting run in the documentation for +[`process::exit`][process-exit]. (In particular, one might be +concerned that the program below will not properly flush and close +`io::stdout`, and/or will fail to detect delayed write failures on +`io::stdout`.) A typical construction is ``` rust fn inner_main() -> Result<(), ErrorT> { @@ -170,6 +161,7 @@ and test functions. [csv-example]: https://doc.rust-lang.org/book/error-handling.html#case-study-a-program-to-read-population-data [to-socket-addrs]: https://doc.rust-lang.org/std/net/trait.ToSocketAddrs.html +[process-exit]: https://doc.rust-lang.org/std/process/fn.exit.html # Detailed design [design]: #detailed-design @@ -189,20 +181,30 @@ importance: the ability to pass an "exit status" up to some outer environment, conventions for what that status means, and an expectation that a diagnostic message will be generated when a program fails - due to a system error. -1. We should avoid making life more complicated for people who don't - care; on the other hand, if the Easiest Thing is also the Right - Thing according to the platform convention, that is better all - around. + due to a system error. However, we should not make things more + complicated for people who don't care. Goal 1 dictates that the new return type for `main` will be `Result` for some T and E. To minimize the necessary changes to existing code that wants to start using `?` in `main`, T should be allowed to be `()`, but other types in that position may also make sense. The appropriate bound for E is unclear; there are plausible -arguments for at least `Error`, `Debug`, and `Display`. This proposal -starts from the narrowest possibility and provides for only -`Result<(), E> where E: Error`. +arguments for at least Error, Debug, and Display. This proposal +selects Display, largely because application error types are not +obliged to implement Error. + +To achieve goal 2 at the same time as goal 1, `main`'s return type +must be allowed to vary from program to program. This can be dealt +with by making the `start` lang item polymorphic (as +[described below](#changes-to-lang-start)) over a +trait which both `()` and `Result<(), E>` implement, and similarly for +[`#[test]` functions](#changes-to-the-test-harness) and +[doctests](#changes-to-doctests). + +Goals 3 and 4 are largely a matter of quality of implementation; at +the level of programmer-visible interfaces, people who don't care are +well-served by not breaking existing code (which is goal 2) and by +removing a way in which `main` is not like other functions (goal 1). ## The `Termination` trait [the-termination-trait]: #the-termination-trait @@ -236,38 +238,29 @@ significant. ## Standard impls of Termination [standard-impls-of-termination]: #standard-impls-of-termination -At least the following implementations of Termination are available in -libstd. I use the ISO C constants `EXIT_SUCCESS` and `EXIT_FAILURE` -for exposition; they are not necesssarily intended to be the exact -values passed to `process::exit`. +At least the following implementations of Termination will be added to +libstd. (Code samples below use the constants `EXIT_SUCCESS` and +`EXIT_FAILURE` for exposition; +[see below](#values-used-for-success-and-failure) for discussion of +what the actual numeric values should be.) The first two are +essential to the proposal: ``` rust -impl Termination for ! { - fn report(self) -> i32 { unreachable!(); } -} - impl Termination for () { fn report(self) -> i32 { EXIT_SUCCESS } } +``` -impl Termination for std::process::ExitStatus { - fn report(self) -> i32 { - self.code().expect("Cannot use a signal ExitStatus to exit") - } -} - -fn print_diagnostics_for_error(err: &E) { - // unspecified, but along the lines of: - if let Some(ref cause) = err.cause() { - print_diagnostics_for_error(cause); - } - eprintln!("{}: {}", get_program_name(), err); -} +This preserves backward compatibility: all existing programs, with +`fn main() -> ()`, will still satisfy the new requirement (which is +effectively `fn main() -> impl Termination`, although the proposal +does not actually depend on impl-trait return types). -impl Termination for Result<(), E> { +``` rust +impl Termination for Result { fn report(self) -> i32 { match self { - Ok(_) => EXIT_SUCCESS, + Ok(val) => val.report(), Err(ref err) => { print_diagnostics_for_error(err); EXIT_FAILURE @@ -277,45 +270,61 @@ impl Termination for Result<(), E> { } ``` -The impl for `!` allows programs that intend to run forever to be more -self-documenting: `fn main() -> !` will satisfy the implicit trait -bound on the return type. It might not be necessary to have code for -this impl in libstd, since `-> !` satisfies `-> ()`, but it should -appear in the reference manual anyway, so people know they can do -that, and it may also be desirable as a backstop against a `main` that +This enables the use of `?` in `main`. The type bound is somewhat +more general than the minimum: we accept any type that satisfies +Termination in the Ok position, not just `()`. This is because, in +the presence of application impls of Termination, it would be +surprising if `fn main() -> FooT` was acceptable but `fn main() +-> Result` wasn't, or vice versa. On the Err side, any +displayable type is acceptable, but its value does not affect the exit +status; this is because it would be surprising if an apparent error +return could produce a successful exit status. (This restriction can +always be relaxed later.) + +Note that `Box` is Display if T is Display, so special treatment of +`Box` is not necessary. + +Two additional impls are not strictly necessary, but are valuable for +concrete known usage scenarios: + +``` rust +impl Termination for ! { + fn report(self) -> i32 { unreachable!(); } +} +``` + +This allows programs that intend to run forever to be more +self-documenting: `fn main() -> !` will satisfy the bound on main's +return type. It might not be necessary to have code for this impl in +libstd, since `-> !` satisfies `-> ()`, but it should appear in the +reference manual anyway, so people know they can do that, and it may +be desirable to include the code as a backstop against a `main` that does somehow return, despite declaring that it doesn't. -The impl for `ExitStatus` allows programs to generate both success and -failure conditions _without_ any errors printed, by returning from -`main`. This is meant to be used by sophisticated programs that do -all of their own error-message printing themselves. -[See below][exit-status] for more discussion and related changes to -`ExitStatus`. - -Additional impls of Termination should be added as ergonomics dictate. -For instance, it may well make sense to provide an impl for -`Result<(), E> where E: Display` or `... where E: Debug`, because -programs may find it convenient to use bare strings as error values, -and application error types are not obliged to be `std::Error`s. -Also, it is unclear to me whether `impl Termination for -Result<(), E>` applies to `Result<(), Box>`. If it doesn't, we -will almost certainly need to add `impl> Termination for -Result<(), E>`. I hope that isn't necessary, because then we might -also need `Rc` and `Arc` and `Cow` and -`RefCell` and ... - -There probably _shouldn't_ be an impl of Termination for Option, -because there are equally strong arguments for None indicating success -and None indicating failure. `bool` is similarly ambiguous. And -there probably shouldn't be an impl for `i32` or `u8` either, because -that would permit the programmer to return arbitrary numbers from -`main` without thinking at all about whether they make sense as exit -statuses. - -A previous revision of this RFC included an impl for `Result -where T: Termination, E: Termination`, but it has been removed from -this version, as some people felt it would allow undesirable behavior. -It can always be added again in the future. +``` rust +impl Termination for bool { + fn report(self) -> i32 { + if (self) { EXIT_SUCCESS } else { EXIT_FAILURE } + } +} +``` + +This impl allows programs to generate both success and failure +conditions for their outer environment _without_ printing any +diagnostics, by returning the appropriate values from `main`, possibly +while also using `?` to report error conditions where diagnostics +_should_ be printed. It is meant to be used by sophisticated programs +that do all, or nearly all, of their own error-message printing +themselves, instead of calling `process::exit` themselves. + +The detailed behavior of `print_diagnostics_for_error` is left +unspecified, but it is guaranteed to write diagnostics to `io::stderr` +that include the `Display` text for the object it is passed, and +without unconditionally calling `panic!`. When the object it is +passed implements `Error` as well as `Display`, it should follow the +`cause` chain if there is one (this may necessitate a separate +Termination impl for `Result<_, Error>`, but that's an implementation +detail). ## Changes to `lang_start` [changes-to-lang-start]: #changes-to-lang-start @@ -354,11 +363,10 @@ fn lang_start use sys_common; use sys_common::thread_info; use thread::Thread; - use process::exit; sys::init(); - exit(unsafe { + sys::process::exit(unsafe { let main_guard = sys::thread::guard::init(); sys::stack_overflow::init(); @@ -373,16 +381,156 @@ fn lang_start sys::args::init(argc, argv); // Let's run some code! - let status = match panic::catch_unwind(main) { - Ok(term) { term.report() } - Err(_) { 101 } - } + let exitcode = panic::catch_unwind(|| main().report()) + .unwrap_or(101); + sys_common::cleanup(); - status + exitcode }); } ``` +## Changes to doctests +[changes-to-doctests]: #changes-to-doctests + +Simple doctests form the body of a `main` function, so they require +only a small modification to rustdoc: when `maketest` sees that it +needs to insert a function head for `main`, it will now write out + +``` rust +fn main () -> Result<(), ErrorT> { + ... + Ok(()) +} +``` + +for some value of `ErrorT` to be worked out +[during deployment](#deployment-plan). This head will work correctly +for function bodies without any uses of `?`, so rustdoc does not need +to parse each function body; it can use this head unconditionally. + +If the doctest specifies its own function head for `main` (visibly or +invisibly), then it is the programmer's responsibility to give it an +appropriate type signature, as for regular `main`. + +## Changes to the `#[test]` harness +[changes-to-the-test-harness]: #changes-to-the-test-harness + +The appropriate semantics for test functions with rich return values +are straightforward: Call the `report` method on the value returned. +If `report` returns a nonzero value, the test has failed. +(Optionally, honor the Automake convention that exit code 77 means +"this test cannot meaningfully be run in this context.") + +The required changes to the test harness are more complicated, because +it supports six different type signatures for test functions: + +``` rust +pub enum TestFn { + StaticTestFn(fn()), + StaticBenchFn(fn(&mut Bencher)), + StaticMetricFn(fn(&mut MetricMap)), + DynTestFn(Box>), + DynMetricFn(Box FnBox<&'a mut MetricMap>>), + DynBenchFn(Box), +} +``` + +All of these need to be generalized to allow any return type that +implements Termination. At the same time, it still needs to be +possible to put TestFn instances into a static array. + +For the static cases, we can avoid changing the test harness at all +with a built-in macro that generates wrapper functions: for example, +given + +``` rust +#[test] +fn test_the_thing() -> Result<(), io::Error> { + let state = setup_the_thing()?; // expected to succeed + do_the_thing(&state)?; // expected to succeed +} + +#[bench] +fn bench_the_thing(b: &mut Bencher) -> Result<(), io::Error> { + let state = setup_the_thing()?; + b.iter(|| { + let rv = do_the_thing(&state); + assert!(rv.is_ok(), "do_the_thing returned {:?}", rv); + }); +} +``` + +after macro expansion we would have + +``` rust +fn test_the_thing_inner() -> Result<(), io::Error> { + let state = setup_the_thing()?; // expected to succeed + do_the_thing(&state)?; // expected to succeed +} + +#[test] +fn test_the_thing() -> () { + let rv = test_the_thing_inner(); + assert_eq!(rv.report(), 0); +} + +fn bench_the_thing_inner(b: &mut Bencher) -> Result<(), io::Error> { + let state = setup_the_thing()?; + b.iter(|| { + let rv = do_the_thing(&state); + assert!(rv.is_ok(), "do_the_thing returned {:?}", rv); + }); +} + +#[bench] +fn bench_the_thing(b: &mut Bencher) -> () { + let rv = bench_the_thing_inner(); + assert_eq!(rv.report(), 0); +} +``` + +and similarly for StaticMetricFn (no example shown because I cannot +find any actual _uses_ of MetricMap anywhere in the stdlib, so I +don't know what a use looks like). + +We cannot synthesize wrapper functions like this for dynamic tests. +We could use trait objects to allow the harness to call +`Termination::report` anyway: for example, assuming that +`runtest::run` returns a Termination type, we would have something +like + +``` rust +pub fn make_test_closure(config: &Config, testpaths: &TestPaths) + -> test::TestFn { + let config = config.clone(); + let testpaths = testpaths.clone(); + test::DynTestFn(Box::new(move |()| -> Box { + Box::new(runtest::run(config, &testpaths)) + })) +} +``` + +But this is not that much of an improvement on just checking the +result inside the closure: + +``` rust +pub fn make_test_closure(config: &Config, testpaths: &TestPaths) + -> test::TestFn { + let config = config.clone(); + let testpaths = testpaths.clone(); + test::DynTestFn(Box::new(move |()| { + let rv = runtest::run(config, &testpaths); + assert_eq(rv.report(), 0); + })) +} +``` + +Considering also that dynamic tests are not documented and rarely used +(the only cases I can find in the stdlib are as an adapter mechanism +within libtest itself, and the compiletest harness) I think it makes +most sense to not support rich return values from dynamic tests for now. + ## `main` in nostd environments [main-in-nostd-environments]: #main-in-nostd-environments @@ -406,122 +554,89 @@ environments to refuse to let you impl Termination yourself. [divergent-main]: https://internals.rust-lang.org/t/allowing-for-main-to-be-divergent-in-embedded-environments/4717 -## Test functions and doctests -[test-functions-and-doctests]: #test-functions-and-doctests - -The harness for `#[test]` functions will need to be changed, similarly -to how the "start" lang item was changed. Tests which return -anything whose `report()` method returns a nonzero value should be -considered to have failed. +## The values used for `EXIT_SUCCESS` and `EXIT_FAILURE` by standard impls of Termination +[values-used-for-success-and-failure]: #values-used-for-success-and-failure -Doctests require a little magic in rustdoc: when `maketest` sees that -it needs to insert a function head for `main`, it should now write out - -``` rust -fn main () -> Result<(), ErrorT> { - ... - Ok(()) -} -``` - -for some value of `ErrorT` TBD. It doesn't need to parse the body of -the test to know whether it should do this; it can just do it -unconditionally. +The C standard only specifies `0`, `EXIT_SUCCESS` and `EXIT_FAILURE` +as arguments to the [`exit`][exit.3] primitive. It does not require +`EXIT_SUCCESS` to be zero, but it does require `exit(0)` to have the +same *effect* as `exit(EXIT_SUCCESS)`. POSIX does require +`EXIT_SUCCESS` to be zero, and the only historical C implementation I +am aware of where `EXIT_SUCCESS` was _not_ zero was for VAX/VMS, which +is probably not a relevant portability target for Rust. +`EXIT_FAILURE` is only required (implicitly in C, explicitly in POSIX) +to be nonzero. It is _usually_ 1; I have not done a thorough survey +to find out if it is ever anything else. + +Within both the Unix and Windows ecosystems, there are several +different semi-conflicting conventions that assign meanings to +specific nonzero exit codes. It might make sense to include some +support for these conventions in the stdlib (e.g. with a module that +provides the same constants as [`sysexits.h`][sysexits]), but that is +beyond the scope of this RFC. What _is_ important, in the context of +this RFC, is for the standard impls of Termination to not get in the +way of any program that wants to use one of those conventions. +Therefore I am proposing that all the standard impls' `report` +functions should use 0 for success and 2 for failure. (It is +important not to use 1, even though `EXIT_FAILURE` is usually 1, +because some existing programs (notably [`grep`][grep.1]) give 1 a +specific meaning; as far as I know, 2 has no specific meaning +anywhere.) -## New constructors for `ExitStatus` -[exit-status]: #exit-status +[exit.3]: http://www.cplusplus.com/reference/cstdlib/exit/ +[sysexits]: https://www.freebsd.org/cgi/man.cgi?query=sysexits +[grep.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html -As mentioned above, we propose to reuse `process::ExitStatus` as a way -for a program to generate both success and failure conditions -_without_ any errors printed, by returning it from `main`. To make -this more convenient, we also propose to add the following new -constructors to `ExitStatus`: +# Deployment Plan +[deployment-plan]: #deployment-plan + +This is a complicated feature; it needs two mostly-orthogonal feature +gates and a multi-phase deployment sequence. + +The first feature gate is `#![feature(rich_main_return)]`, which must +be enabled to write a main function, test function, or doctest that +returns something other than `()`. This is not a normal unstable-feature +annotation; it has more in common with a lint check and may need to be +implemented as such. It will probably be possible to stabilize this +feature quickly—one or two releases after it is initially implemented. + +The second feature gate is `#![feature(termination_trait)]`, which +must be enabled to make *explicit* use of the Termination trait, +either by writing new impls of it, or by calling `report` directly. +However, it is *not* necessary to enable this feature gate to merely +return rich values from main, test functions, etc (because in that +case the call to `report` is in stdlib code). I *think* this is the +semantic of an ordinary unstable-feature annotation on Termination, +with appropriate use-this annotations within the stdlib. This feature +should not be stabilized for at least a full release after the +stabilization of the `rich_main_return` feature, because it has more +complicated backward compatibility implications, and because it's not +going to be used very often so it will take longer to gain experience +with it. + +In addition to these feature gates, rustdoc will initially not change +its template for `main`. In order to use `?` in a doctest, at first +it will be necessary for the doctest to specify its own function head. +For instance, the `ToSocketAddrs` example from the +["motivation" section](#motivation) will initially need to be written ``` rust -impl ExitStatus { - /// Return an ExitStatus value representing success. - /// (ExitStatus::ok()).success() is guaranteed to be true, and - /// (ExitStatus::ok()).code() is guaranteed to be Some(0). - pub fn ok() -> Self; - - /// Return an ExitStatus value representing failure. - /// (ExitStatus::failure()).success() is guaranteed to be false, and - /// (ExitStatus::failure()).code() is guaranteed to be Some(n), - /// for some unspecified n, 1 < n < 64. - pub fn failure() -> Self; - - /// Return an ExitStatus value representing a specific exit code. - /// The difference between this method and ExitStatusExt::from_raw - /// is that this method can only be used to produce ExitStatus - /// values that will pass unmodified through the operating system - /// primitive "exit" and "wait" operations on all supported - /// platforms. (Conveniently, this is exactly the range of u8.) - pub fn from_code(code: u8) -> Self; -} +/// # #![feature(rich_main_return)] +/// # fn main() -> Result<(), io::Error> { +/// use std::net::UdpSocket; +/// let port = 12345; +/// let mut udp_s = UdpSocket::bind(("127.0.0.1", port))?; +/// udp_s.send_to(&[7], (ip, 23451))?; +/// # Ok(()) +/// # } ``` -The first method's name is `ok` because `success` is already taken. - -## Unix-specific refinements -[unix-specific-refinements]: #unix-specific-refinements - -The C standard only specifies `0`, `EXIT_SUCCESS` and `EXIT_FAILURE` -as arguments to the [`exit`][exit.3] primitive. (`EXIT_SUCCESS` is -not guaranteed to have the value 0, but calling `exit(0)` *is* -guaranteed to have the same effect as calling `exit(EXIT_SUCCESS)`; -several versions of the `exit` manpage are incorrect on this point.) -Any other argument has an implementation-defined effect. - -Within the Unix ecosystem, `exit` can be relied upon to pass values in -the range 0 through 255 up to the parent process; this is the range -proposed for `ExitStatus::from_code`. There is no general agreement -on the meaning of specific nonzero exit codes, but there are many -contexts that give specific codes a meaning, such as: - -* POSIX reserves status 127 for certain internal failures in `system` - and `posix_spawn` that cannot practically be reported via `errno`. - -* [`grep`][grep.1] is specified to exit with status 0 if it found a - match, 1 if it found no matches, and an unspecified value greater - than 1 if an error occurred. - -* [Automake's support for basic testing][automake-tests] defines status - 77 to mean "test skipped" and status 99 to mean "hard error" - (I'm not sure precisely what "hard error" is for, but probably - something like "an error so severe that the entire test run should - be abandoned"). - -* The BSDs have, for a long time, provided a header - [`sysexits.h`][sysexits] that defines a fairly rich set of exit - codes for general use, but as far as I know these have never seen - wide adoption. Of these, `EX_USAGE` (code 64) for command-line - syntax errors is probably the most useful. - -* Rust itself uses status 101 for `panic!`, although arguably it - should be calling `abort` instead. (`abort` on Unix systems sends a - different status to the parent than any value you can pass to `exit`.) - -It is unnecessary to put most of this stuff into the Rust stdlib, -especially as some of these conventions contradict each other. -However, the stdlib should not get in the way of a program that -intends to conform to any of the above. This can be done with the -following refinements: - -* All of the implementations of `Termination` in the stdlib, except - the one for `ExitStatus` itself, are guaranteed to behave as-if they - use only `ExitStatus::ok()` and `ExitStatus::failure()`. - -* The value used by `ExitStatus::failure()` is guaranteed to be - greater than 1 and less than 64. This avoids collisions with all of - the above conventions. (I recommend we actually use 2, because - people may think that any larger value has a specific meaning, and - then waste time trying to find out what it is.) - -[exit.3]: http://www.cplusplus.com/reference/cstdlib/exit/ -[grep.1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html -[automake-tests]: https://www.gnu.org/software/automake/manual/html_node/Scripts_002dbased-Testsuites.html -[sysexits]: https://www.freebsd.org/cgi/man.cgi?query=sysexits - +After enough doctests have been updated, we can survey them to learn +what the most appropriate default function signature for doctest +main is, and only then should rustdoc's template be changed. +(Ideally, this would happen at the same time that `rich_main_return` +is stabilized, but it might need to wait longer, depending on how +enthusiastic people are about changing their doctests.) # How We Teach This [how-we-teach-this]: #how-we-teach-this @@ -548,7 +663,8 @@ will still need to use `main` primarily for error reporting. Returning to the [CSV-parsing example][csv-example], a "professional" version of the program might look something like this (assume all of the boilerplate involved in the definition of `AppError` is just off -the top of your screen): +the top of your screen; also assume that `impl Termination for bool` +is available): ``` rust struct Args { @@ -582,20 +698,20 @@ fn process(city: &String, data_path: &Path) -> Result { } } -fn main() -> ExitStatus { +fn main() -> bool { match parse_args() { Err(err) => { eprintln!("{}", err); - ExitStatus::failure() + false }, Ok(args) => { match process(&args.city, &args.data_path) { Err(err) => { eprintln!("{}: {}: {}", args.progname, args.data_path, err); - ExitStatus::failure() + false }, - Ok(_) => ExitStatus::ok() + Ok(_) => true } } } @@ -609,20 +725,16 @@ but it _is_ using the generalized `main` return value. The [issue #39849][issue39849]) may well enable shortening this `main` and/or putting `parse_args` and `process` back inline. -Tutorial examples should probably still begin with `fn main() -> ()` -until the tutorial gets to the point where it starts explaining why -`panic!` and `unwrap` are not for "normal errors". The `Termination` -trait should also be explained at that point, to illuminate _how_ -`Result`s returned from `main` turn into error messages and exit -statuses, but as a thing that most programs will not need to deal with -directly. +Tutorial examples should still begin with `fn main() -> ()` until the +tutorial gets to the point where it starts explaining why `panic!` and +`unwrap` are not for "normal errors". The `Termination` trait should +also be explained at that point, to illuminate _how_ `Result`s +returned from `main` turn into error messages and exit statuses, but +as a thing that most programs will not need to deal with directly. -Discussion of `ExitStatus::from_code` should be reserved for an -advanced-topics section talking about interoperation with the Unix -command-line ecosystem. - -Doctest examples can freely use `?` with no extra boilerplate; -`#[test]` examples may need their boilerplate adjusted. +Once the doctest default template is changed, doctest examples can +freely use `?` with no extra boilerplate, but `#[test]` examples +involving `?` will need their boilerplate adjusted. [rfc243]: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md [issue39849]: https://github.com/rust-lang/rust/issues/39849 @@ -652,31 +764,34 @@ out all the boilerplate by hand, but it's still there. We need to decide what to call the new trait. The names proposed in the pre-RFC thread were `Terminate`, which I like OK but have changed -to `Termination` because return value traits should be nouns, and -`Fallible`, which feels much too general, but could be OK if there -were other uses for it? Relatedly, it is conceivable that there are -other uses for `Termination` in the existing standard library, but I -can't think of any right now. (Thread join was mentioned in the -[pre-RFC][pre-rfc], but that can already relay anything that's `Send`, -so I don't see that it adds value there.) - -We also need to decide where the trait should live. One obvious place -is in `std::process`, because that is where `exit(i32)` is; on the -other hand, this is basic enough that it may make sense to put at -least some of it in libcore. Note that the `start` lang item is not -in libcore. - -I don't know what impls of `Termination` should be available beyond -the ones listed above, nor do I know what impls should be in libcore. - -It may make sense to provide a type alias like - - type MayFail = Result<(), Box>; - -which would mean less boilerplate in the most common case for `main`. -However, this may be too single-purpose for a global-prelude type -alias, and if we ever get return type deduction, that would completely -supersede it, but we'd have burned the name forever. +to `Termination` because value traits should be nouns, and `Fallible`, +which feels much too general, but could be OK if there were other uses +for it? Relatedly, it is conceivable that there are other uses for +`Termination` in the existing standard library, but I can't think of +any right now. (Thread join was mentioned in the [pre-RFC][pre-rfc], +but that can already relay anything that's `Send`, so I don't see that +it adds value there.) + +We may discover during the deployment process that we want more impls +for Termination. The question of what type rustdoc should use for +its default `main` template is explicitly deferred till during +deployment. + +Some of the components of this proposal may belong in libcore, but +note that the `start` lang item is not in libcore. It should not be a +problem to move pieces from libstd to libcore later. + +All of the code samples in this RFC need to be reviewed for +correctness and proper use of idiom. + +# Related Proposals +[related-proposals]: #related-proposals + +This proposal formerly included changes to `process::ExitStatus` +intended to make it usable as a `main` return type. That has now been +spun off as its own [pre-RFC][exit-status-pre], so that we can take our +time to work through the portability issues involved with going beyond +C's simple success/failure dichotomy without holding up this project. There is an outstanding proposal to [generalize `?`][try-trait] (see also RFC issues [#1718][rfc-i1718] and [#1859][rfc-i1859]); I @@ -685,43 +800,12 @@ sure it doesn't conflict and we should also figure out whether we would need more impls of `Termination` to make them play well together. -Limiting `ExitStatus::from_code` to the range of a `u8` is somewhat -arbitrary. There is an argument for allowing the full range of `i32`, -consistent with `process::exit`. On Windows, arbitrary `DWORD` -quantities can pass through `ExitProcess` to `GetExitCodeProcess`, -except that the value 259 is reserved to mean "this process is still -running." On fully POSIX-compliant systems, it is possible to pass an -arbitrary `int` through `_exit` to `waitid`, but not `waitpid`; -`waitid` is not available on all contemporary Unixes (e.g. NetBSD -doesn't have it) and transmission of arbitrary `int`s is not -implemented by all those that do have it (e.g. Linux and FreeBSD -truncate the exit status to `u8`, same as for `waitpid`, and OSX -truncates it to 24 *signed* bits). The core Rust stdlib generally -refrains from providing features that work on some but not all -supported platforms, so I think the restriction to `u8` is -appropriate. - -There is also an argument for limiting `ExitStatus::from_code` to the -range 0 through 127, because some shells conflate exit codes above 127 -with *signal* statuses (e.g. in Bourne shell, `$?` having the value -130 could mean either exit code 130, or killed by signal 2), and -because some old C libraries would sign- rather than zero-extend in -`WEXITSTATUS`. However, these would not affect Rust programs -communicating with other Rust programs so I do not think the -limitation is appropriate. Also, there is currently no way to express -the range 0 through 127 in the type system. - -Most operating systems accept only a scalar exit status, but -[Plan 9][], uniquely (to my knowledge), takes an entire string (see -[`exits(2)`][exits.2]). Do we care? If we care, what do we do about -it? - The ergonomics of `?` in general would be improved by autowrapping fall-off-the-end return values in `Ok` if they're not already -`Result`s, but that's another proposal. +`Result`s, but that's [still another proposal][autowrap-return]. +[exit-status-pre]: https://internals.rust-lang.org/t/mini-pre-rfc-redesigning-process-exitstatus/5426 [try-trait]: https://github.com/nikomatsakis/rfcs/blob/try-trait/text/0000-try-trait.md [rfc-i1718]: https://github.com/rust-lang/rfcs/issues/1718 [rfc-i1859]: https://github.com/rust-lang/rfcs/issues/1859 -[Plan 9]: http://www.cs.bell-labs.com/plan9/index.html -[exits.2]: http://plan9.bell-labs.com/magic/man2html/2/exits +[autowrap-return]: https://internals.rust-lang.org/t/pre-rfc-throwing-functions/5419 From b44140540cdfedd087b686cd4c3edfca5bcbd194 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Mon, 26 Jun 2017 12:20:04 -0400 Subject: [PATCH 8/9] Typo fixes, add one more unresolved question --- text/0000-ques-in-main.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/text/0000-ques-in-main.md b/text/0000-ques-in-main.md index 3c36ffefdaa..d02036ef80d 100644 --- a/text/0000-ques-in-main.md +++ b/text/0000-ques-in-main.md @@ -12,7 +12,7 @@ functions and doctests. To make this possible, the return type of these functions are generalized from `()` to a new trait, provisionally called `Termination`. libstd implements this trait for a set of types -partially TBD (see [list below](#standard-impls-of-termination); +partially TBD (see [list below](#standard-impls-of-termination)); applications can provide impls themselves if they want. There is no magic added to function signatures in rustc. If you want @@ -198,8 +198,8 @@ must be allowed to vary from program to program. This can be dealt with by making the `start` lang item polymorphic (as [described below](#changes-to-lang-start)) over a trait which both `()` and `Result<(), E>` implement, and similarly for -[`#[test]` functions](#changes-to-the-test-harness) and -[doctests](#changes-to-doctests). +[doctests](#changes-to-doctests) and +[`#[test]` functions](#changes-to-the-test-harness). Goals 3 and 4 are largely a matter of quality of implementation; at the level of programmer-visible interfaces, people who don't care are @@ -781,6 +781,9 @@ Some of the components of this proposal may belong in libcore, but note that the `start` lang item is not in libcore. It should not be a problem to move pieces from libstd to libcore later. +It would be nice if we could figure out a way to enable use of `?` in +_dynamic_ test-harness tests, but I do not think this is an urgent problem. + All of the code samples in this RFC need to be reviewed for correctness and proper use of idiom. @@ -800,9 +803,11 @@ sure it doesn't conflict and we should also figure out whether we would need more impls of `Termination` to make them play well together. -The ergonomics of `?` in general would be improved by autowrapping -fall-off-the-end return values in `Ok` if they're not already -`Result`s, but that's [still another proposal][autowrap-return]. +There is also an outstanding proposal to improve the ergonomics of +`?`-using functions by +[autowrapping fall-off-the-end return values in `Ok`][autowrap-return]; +it would play well with this proposal, but is not necessary nor does +it conflict. [exit-status-pre]: https://internals.rust-lang.org/t/mini-pre-rfc-redesigning-process-exitstatus/5426 [try-trait]: https://github.com/nikomatsakis/rfcs/blob/try-trait/text/0000-try-trait.md From 4f3308a12d82c9b8038a614f43c74b287291b246 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Mon, 17 Jul 2017 15:35:32 -0700 Subject: [PATCH 9/9] RFC 1937 is: `?` in `main` --- text/{0000-ques-in-main.md => 1937-ques-in-main.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename text/{0000-ques-in-main.md => 1937-ques-in-main.md} (99%) diff --git a/text/0000-ques-in-main.md b/text/1937-ques-in-main.md similarity index 99% rename from text/0000-ques-in-main.md rename to text/1937-ques-in-main.md index d02036ef80d..6f16921424f 100644 --- a/text/0000-ques-in-main.md +++ b/text/1937-ques-in-main.md @@ -1,7 +1,7 @@ - Feature Name: ques_in_main - Start Date: 2017-02-22 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: https://github.com/rust-lang/rfcs/pull/1937 +- Rust Issue: https://github.com/rust-lang/rust/issues/43301 # Summary [summary]: #summary