Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: ? in main #1937

Merged
merged 9 commits into from
Jul 17, 2017
Merged

RFC: ? in main #1937

merged 9 commits into from
Jul 17, 2017

Conversation

zackw
Copy link
Contributor

@zackw zackw commented Mar 1, 2017

Rendered.

Would resolve #1176. Pre-RFC discussion. See also #1718, #1859, and the internals thread about main returning !.

Edit: Updated render link post-merge

@nikomatsakis nikomatsakis added T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC. labels Mar 1, 2017
@SimonSapin
Copy link
Contributor

I like the general idea and approach. A few comments in the details:

The boilerplate shown above 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.

I assume this means types implementing the std::error::Error trait. I’d rather not make this trait required for main, I personally find it to be useless in practice. I don’t bother implementing it for my error types unless someone else asks for it.

Later in the RFC you propose impl<T: Termination, E: Termination> Termination for Result<T, E> which does not involve the Error trait. This seems to contradict the part quoted above.

The stdlib provides EXIT_SUCCESS and EXIT_FAILURE constants in std::process […] 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.

Why? What’s wrong with EXIT_FAILURE = 1?

@Ericson2314
Copy link
Contributor

Ericson2314 commented Mar 1, 2017

In various places, we've talked about a "needs-provides" feature for crates to declare items which are defined in a downstream crate. Ultimately, I do want this, and I want it to provide the underpinning for the waymain is handled, all the way back to crt0 and friends. [We'd still need some main-specific hacks for back compat, but hopefully just sugar.]

Anticipating this, I'd like std-less programs to stay the same, and only those linking std to benefit from this. (One can imagine std declares a new main with an impl Terminate return type, and then implements core's main with a shim calling the new main.)

It's fine if ! implements Terminate, but I don't want this to be the solution for embedded systems. On platforms where this is required (not with Terminate it would be optional), it's better for the runtime library to play the role of crt0, defining the true entry point along as a shim to call a declared main which must return !, no Terminate needed.

CC @japaric

@est31
Copy link
Member

est31 commented Mar 1, 2017

👍 but it should work with the try! macro as well. It seems right now that it will be.

@zackw
Copy link
Contributor Author

zackw commented Mar 2, 2017

@SimonSapin

I assume this means types implementing the std::error::Error trait. I’d rather not make this trait required for main, I personally find it to be useless in practice.

Yes, that's what I meant. i'm not wedded to it, but I hesitate to make all Display types implement Termination, because for many of them (all the numeric types, for instance) it's not obvious what the mapping from value to exit status, or even to success/failure, should be.

I suppose we could provide Termination for Result<_, E> where E: Display and map it to EXIT_FAILURE but is there a way to ensure that if E impls Termination and Display, we pick the Termination variant?

What’s wrong with EXIT_FAILURE = 1?

This is part of the stdlib staying out of the way of applications that want to implement particular Unix conventions. 1 is used by some programs to mean "no error but no matches found". The stdlib's Termination impls are all for errors, so it should avoid 1.

(This is not an essential part of the proposal but I do think it is worthwhile.)

`Termination` don't make sense in context.

There are also environments where
[returning from `main` constitutes a _bug_.][divergent-main] If you
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are also environments where [returning from main constitutes a bug.]

It's totally fine to return from main (vanilla fn main()) in embedded / no_std / bare metal / microcontroller systems; I do it all the time. Just make sure you don't return from the entry point function (e.g. reset handler) because that's UB. On bare metal systems, you are in charge of everything so this is doable. I have been using this entry point for my ARM Cortex-M programs for a while now:

/// Reset handler
#[no_mangle]
pub unsafe fn _start() -> ! {
    // rustc synthesized `main` symbol. cf. `start` lang item
    extern "C" {
        fn main(argc: usize, argv: *const *const u8) -> isize;
    }

    zero_bss_section();
    initialize_data_section();

    main(0, ptr::null());

    // Go into "reactive" mode: service interrupts as they occur and sleep when there's nothing to do
    loop {
        asm!("wfi" :::: "volatile");
    }
}

So a microcontroller program like this is fine:

fn main() {
    initialize_stuff();
}

// interrupt handler
fn on_timeout() {
    LED.toggle();
}

As well as this one with a "main loop":

fn main() {
    initialize_stuff();

    loop {
        LED.toggle();
        delay_ms(500);
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not at all a microcontroller person. Divergent main is in this RFC because it was explicitly asked for over here. Are you saying that that's not actually a useful feature?


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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be enough to just give #[test] functions the same shim that we give to main.

This would not be compatible with no_std test runners like utest.

Supporting #[test] fn() -> Result<_, _> is actually simple. The test crate already has an enum that represents #[test] fn(), #[bench] fn(&mut Bencher), etc. We just have to add a new variant that represents the Result variant and lift the rustc restriction that functions with #[test] attribute must have signature fn(). Then it would be the job of the test runner to handle the new case.

But that would be a different RFC than this one.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it is already captured anywhere, but I'd find this useful to be able to mark tests as "skipped at runtime".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@japaric

[Giving #[test] functions the same shim as main] would not be compatible with no_std test runners like utest.

I don't follow - the whole point of the shim is to make it so external library code that expects an entry point returning () doesn't have to know or care about this RFC. Why wouldn't that work for utest?

@lucab

... to be able to mark tests as "skipped at runtime"

I want that too (e.g. it would help with #39798), and it's a very common feature in test harnesses generally, but I'm not sure how to wedge it into this RFC. I'll think about it some more, but would you mind thinking about it too?

people coming to Rust from other languages may find this _less_
surprising than the status quo.

# Alternatives
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or encourage the use of templates like quickstart. That's how I start all my toy and non-toy std apps today. Granted, that wouldn't help with doctests / documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll add that to the alternatives section.

if let Some(ref cause) = self.cause() {
cause.write_diagnostic(progname, stream);
}
writeln!(stream, "{}: {}\n", progname, self.description());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That means when we use chained error the result will be reported like this, repeating the progname many times?

my_program: file not found: /foo/bar
my_program: cannot open configuration file
my_program: initialization failure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is what Unixy programs are expected to do. The "//unspecified but not entirely unlike:" comment is meant to give wiggle room to match the expectations of other environments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced Windows has an expectation for us to match. The following tools are all in %WINDIR%\System32, and some aren't even consistent with themselves:

C:\Users\Guest>findstr pgjearpgje aerg jaeiajrpoerijgaerijgaeogirje
FINDSTR: Cannot open aerg
FINDSTR: Cannot open jaeiajrpoerijgaerijgaeogirje

C:\Users\Guest>find "fawerfawef" awefa awegfawergar
File not found - AWEFA
File not found - AWEGFAWERGAR

C:\Users\Guest>where /E
ERROR: Invalid argument or option - '/E'.
Type "WHERE /?" for usage help.

C:\Users\Guest>find asdfawerfawef awefawefawefw awegfawergar
FIND: Parameter format not correct

C:\Users\Guest>convert 123
Invalid drive specification.

C:\Users\Guest>taskkill /im aergaerge
ERROR: The process "aergaerge" not found.

Makes me worry that anything we pick would be widely considered surprising.


``` rust
trait Termination {
fn write_diagnostic(&self, progname: &str, stream: &mut Write) -> ();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::io::Write or core::fmt::Write?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. I was thinking std::io::Write because that's what set_panic takes, but depending on how much needs to be in libcore, it could wind up better the other one, I suppose...

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Termination should be put in libcore, probably as a lang-item. I don't think it is a good idea for the compiler to use a hard-coded list of valid return types, where the path std::error::Error, and worse, the OS-dependent std::process::TermStatus need to be known by the compiler.

(Also note that the current ? operator lowering does not refer to core::result::Result directly, but through the trait core::ops::Carrier.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you think of moving std::process::exit into libcore?

Copy link
Member

@kennytm kennytm Mar 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zackw The low-level entry point needs to return an i32, there is no need to use std::process::exit to return the exit code.

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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plan 9 is not in https://forge.rust-lang.org/platform-support.html, I guess not (yet).

Plus, an integer can be easily formatted to a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point here is that hypothetically people writing Plan 9-native programs in Rust would want full control over the string passed to exits().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's worth complicating the interface with. People who want full control can call a platform-specific exit function.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows has %ERRORLEVEL% too 😢

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you are meaning to get at with this? The (but not on windows) parenthetical is because Windows does not truncate the value passed to ExitProcess, so it is correct to create TermStatus quantities from arbitrary i32's on Windows. (Technically, POSIX-compliant systems are supposed to make the full value passed to _exit available to a parent that retrieves it with waitid instead of waitpid, but I'm not aware of any actual Unix that meets that requirement.)

@scottmcm
Copy link
Member

scottmcm commented Mar 4, 2017

I like what you said about not having i32:Termination, so TermStatus 👍 (Though I named it ExitCode in my head, because of priming.) Newtypes (or maybe #[repr(i32)] enum) FTW. Instead of panicking for out-of-range things, what about #[cfg] new methods using RFC 1868 and i32:From<TermStatus>? Would be nice to have TermStatus::Success and TermStatus::Failure too. (And, eventually, have process::exit take it, but that's another conversation.)

I love that fn main() -> ! { loop { ... } } works so naturally. Hooray for good type systems 🎉

For the trait: it feels weird to have two methods always called one after the other where one returns unit. Why not just fn handle(self, progname: &str, stream: &mut Write) -> TermStatus? (Or maybe -> Option<TermStatus>, if we want to allow abstaining from specifying one? Not sure if that's helpful...) Consuming self means that it can't accidentally run twice and thus won't accidentally write the message twice.

On progname and stream: I look at them, see that most of the impls in the RFC don't actually use them, and wonder whether they're worth it. They're making the impl have writeln!(stream, "{}: {}\n", progname, self.description()); instead of eprintln!("{}: {}\n", std::env::progname(), self.description()); (well, with RFC 1869). The progname function seems like a useful thing for error reporting, but once the method exists, the argument becomes mostly unnecessary, so I don't think either should be part of the RFC. And eprintln! seems like a better option than passing an argument that's always std::io::LOCAL_STDERR. (Not having the trait talk about things like process names or streams may also help with putting it in core...)

The two things above bring the trait down to just something like

trait Termination {
    fn report(self) -> TermStatus;
}

(I tried to give the method a name that implies its impurity, but I suspect there's a better one.)

Translating the impls from the RFC ends up something like this, which I think looks quite nice:

impl Termination for ! {
    fn report(self) -> TermStatus { unreachable!() }
}

impl Termination for () {
    fn report(self) -> TermStatus { EXIT_SUCCESS }
}

impl Termination for TermStatus {
    fn report(self) -> TermStatus { self }
}

// unspecified and private anyway, but not entirely unlike this:
fn report_recursive(error: &Error) {
    if let Some(ref cause) = error.cause() {
        cause.report_recursive());
    }
    eprintln!("{}: {}", std::env::progname(), error.description());
}

impl<E: Error> Termination for E {
    fn report(self) -> TermStatus {
        report_recursive(&self);
        TermStatus::Failure
    }
}

impl<T: Termination, E: Termination> Termination for Result<T, E> {
    fn report(self) -> TermStatus {
        match self {
            Ok(ok) => ok.report(),
            Err(err) => match err.report() {
                TermStatus::Success => TermStatus::Failure,
                e => e
            }
        }
    }
}

I'm glad you like the result owl example, but I'm still not convinced by the Result impl multiplexing back to the Termination trait. It enables a bunch of weird things, like returning io::Error instead of io::Result<()>. I like @nikomatsakis's suggestion of just having impl<E: Display> Termination for Result<(), E> that only returns TermStatus::Success or TermStatus::Failure and using custom types for anything else. Mixing cases seem fundamentally troublesome, since you have no idea if, say, Result<TermStatus, Box<Error>> might get some error that reports as the 1 that you're trying to use for vacuous-success. (I left out the non-unit success case, since I'm not convinced by it. I feel like people would expect Result<i32, E> to use the i32 as the termination status. And I'm not sure that return-from-main-as-print is something worth encouraging. I can just picture someone putting fn main() -> Result<&'static str, !> { Ok("Hello World") } in a perverse blog post...)

@nikomatsakis nikomatsakis changed the title RFC draft: ? in main RFC: ? in main Mar 4, 2017
@nikomatsakis
Copy link
Contributor

A couple of thoughts:


I think I may prefer @scottmcm's simplified traits. Ultimately, I would definitely appreciate @rust-lang/libs team feedback on "fine-tuning" the traits for ergonomics and usability, as well as the set of default impls -- this is why I tagged the RFC with T-libs.

(I think I still prefer supporting just Result<(), E>, perhaps where E: Error instead of `E: Display. The RFC text seems to suggest that this it the impl it will provide, but it actually specifies another, so I'm not sure which you had in mind, @zackw.)


The main thing I wanted to comment on, though, was the "changes to main" section. Let's start with this paragraph:

From the perspective of the code that calls main, its signature is now generic:

fn<T: Termination> 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 think there is some confusion here about "input" vs "output" types (also called the "universal" vs "existential", or "any" vs "some"). Specifically the T in your "generic main" example is an input type -- meaning that main doesn't get to choose what T it is invoked with. But that's not what we want. We want the main() function to specify the kind of value it returns; hence it is an "output" type. If we want to write the signature generically, then, we could write it with impl Trait, which allows us to hide the output type (but it will be inferred from the fn body):

fn main() -> impl Termination { ... }

Or of course we could write it, as we do today, with a concrete type:

fn main() { } // -> ()

If you think of it in terms of traits, there is kind of a trait like:

trait Main {
    type Output;
    fn main() -> Self::Output;
}

Continuing on to to the next paragraph, I think that looking at things in terms of "input" vs "output" types is also helpful here:

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<Termination> probably is possible but requires a heap allocation, which is undesirable in no-std contexts.

I think the gist of @alexcrichton's comment on the internals thread (which, btw, it's probably worth linking to in the RFC), was that the actual starting point for execution is the lang_start "lang item" in the standard library, not main. Currently, that lang-item has a monomorphic signature:

fn lang_start(main: fn(), argc: isize, argv: *const *const u8) -> isize
// the actual code uses main: *const u8, but transmutes it to `fn()` later

but there is no reason it can't have a generic signature:

fn lang_start<T: Termination>(main: fn() -> T, argc: isize, argv: *const *const u8) -> isize

The key point is that the T here is an input type (unlike with main()). That is, for any given binary, there is some termination type X, which is determined by the body of the main() function, and the compiler will create a monomorphized copy of lang_start specialized to X (and exactly one, since there must be only one starting point).

@eddyb
Copy link
Member

eddyb commented Mar 4, 2017

Heh, yeah, lang_start being monomorphic is a hold over from a time before Rust had where clauses and a real trait system, there's no real reason to keep it that way.

@kornelski
Copy link
Contributor

I feel like there are two distinct features here:

  1. Overloaded return type
  2. Acting on main()'s returned value

The overloaded return type is handled as a bit of a fudged half-magic here. Maybe it'd be better to have a separate RFC for it?

@kornelski
Copy link
Contributor

kornelski commented Mar 4, 2017

This RFC assumes that returning Terminate from main will be used for real programs, not just for short example code.

However, for real output from the program I need to have 100% control over how the error is formatted. For example, I need different formatting depending on program's verbose flag, and I don't want the program name, but an "error: " prefix, and I don't want to display all error causes, only some of them that don't expose implementation details, etc.

Implementation of write_diagnostic is endlessly bikesheddable. Unfortunately, customization requires wrapping errors in a newtype with Termination trait implementation, which takes more code than just having a match in main.

@scottmcm
Copy link
Member

scottmcm commented Mar 5, 2017

Interesting, @pornel. I agree that the output format is endlessly bikesheddable. Perhaps the only way to deal with that is to re-use an existing shed: unwrap's output.

What if an Err from main still panic!ed? That's consistent with the "explicit exceptions" interpretation of Result: it gives a debug-formatted error if you let an "uncaught exception" escape main, like in many (most?) languages with exceptions. And the "just having a match in main" is perfectly analogous to putting a catch-all block to handle those exceptions if you don't want them exposed--especially if you can literally wrap it in a catch block to do so with that work now in flight. Similarly, turning an error into a panic to fail the test is perfectly reasonable, as the test runner needs to catch panics anyway. And the doc examples panicking on errors wasn't the problem, just that it happened because of syntactic unwraps. (Now, this does require considering "error returned from main" as "a bug" in order to not blatantly violate design point 3, so someone living in the hosted environment world should push back on everything I just said.)

If that's the case, then it'd bring things down to just

// Same trait, and same impls for !, (), and TermStatus

impl<T: Termination, E: Debug> Termination for Result<T, E> {
    fn report(self) -> TermStatus {
        self.expect("unhandled `Err` value").report()
    }
}

One thing I quite like about that is that it resolves my result-multiplexing-to-Termination complaints. Having fn main() -> io::Result<TermStatus> becomes reasonable, since any overlap in exit code already exists with existing panic possibilities. (And those might go away to if the RFC's "use abort instead of exit" suggestion is adopted.) And delegating on success is certainly valuable; being able to use ? inside an "infinite" loop by returning io::Result<!> is pretty cool.

now generic:

``` rust
fn<T: Termination> main() -> T { ... }
Copy link
Contributor

@mglagla mglagla Mar 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Angle brackets belong after the function name: fn main<T: Termination>() -> T { ... }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is pseudosyntax? Since the only way this proposal can work is fn main() -> T where T: Termination, but T has to be a concrete type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, pseudosyntax. Also, any time you see me write something that's not quite right, please remember that I only started learning Rust this past January. (Yeah, I like jumping in at the deep end 😉)

@joshtriplett
Copy link
Member

joshtriplett commented Mar 6, 2017

I love this proposal. This should allow short examples to stop using .unwrap() and similar, and instead use error handling that works everywhere, whether in main or deeper.

However, I would find it exceptionally confusing if Rust's EXIT_FAILURE had a different value than C's EXIT_FAILURE. Having EXIT_FAILURE map to 1 (if C does so) seems completely fine. If you really want a value that maps to something other than C's EXIT_FAILURE value, call it something else entirely. (I do like the idea of having the default exit code for the Error implementation use something other than 1; I'd just like to avoid the confusion of calling that value EXIT_FAILURE.)

@joshtriplett
Copy link
Member

Also, one bikeshedding nit: TermStatus seems confusing due to abbreviating "Termination" as "Term", because in many contexts (especially that of a command-line tool) "Term" more naturally refers to "Terminal". Please consider ExitCode or similar. (See also @scottmcm's comment.)

@alexcrichton
Copy link
Member

I also tend to prefer @scottmcm's simplified traits, notably consuming self and only having one method to implement rather than two separated ones (also gives the flexibility of choosing the output stream). I'll also note that a TermStatus struct would be great for cross-platform creation functions (e.g. success and failure) and it'd also be a great location to have platform-specific extension traits for the various types of return values across OSes.

I think I also agree with @nikomatsakis in that Result<T, E> is probably a little too generic for returning from main and I'd be fine with just supporting Result<(), E>.

Finally thanks so much for mentioning doctests here! I think you proposed solution will work great (and we can use crater to confirm). I wonder if perhaps we could default to -> Result<(), Box<Error>> as a type which should work for E: Error, right?

@joshtriplett
Copy link
Member

@alexcrichton

I think I also agree with @nikomatsakis in that Result<T, E> is probably a little too generic for returning from main and I'd be fine with just supporting Result<(), E>.

That wouldn't work if you want to both support error-handling (using the E) and return a 0/1 exit code (using the T).

@alexcrichton
Copy link
Member

Well, to be clear, literally everything is already supported today, you can just use std::process::exit. The motivation here is to use ? more liberally in main to have examples, snippets, etc, "just work" more often. Notably, the motivation is to not move as much after-main logic as possible to the return type of main. I feel like it'd be quite reasonable to say that if you want to deal with Result<bool, Error> you'd just write
.map(handle_bool_exit) and call it a day

@joshtriplett
Copy link
Member

@alexcrichton std::process::exit doesn't run destructors, making it necessary to run it at a point where any destructors have already run. That often motivates the same kind of separation into a real_main() that ? does. Handling it by returning Result<ExitCode, Error> from main() seems much simpler.

@nikomatsakis
Copy link
Contributor

I think there is an interesting question as to whether this kind of return value should be something formatted "nicely enough" that for simple uses it might be acceptable to present to the user, or whether it should display something very "debug-oriented", kind of analogous to an uncaught exception in a Java program (etc). The latter is sort of what I naively expected, but I think the approach the RFC takes is more the former, and it has its appeal as well.

@joshtriplett
Copy link
Member

@nikomatsakis This loses a huge amount of value if it looks like a panic! or unwrap() or expect(). I'd like something that looks appropriate to present to a user, so I can actually use it rather than having to replace it.

@aturon aturon merged commit c379f5d into rust-lang:master Jul 17, 2017
@aturon
Copy link
Member

aturon commented Jul 17, 2017

Huzzah! This RFC has been merged! Tracking issue.

Thanks, all, for the long and vigorous discussion. This is going to be a great improvement for Rust.

@radix
Copy link

radix commented Jul 18, 2017

can the "Rendered" link in the PR description be updated so it points to the rust-lang repo instead of the personal one (which is now 404ing)?

@scottmcm
Copy link
Member

@radix Updated.

perlun added a commit to perlun/rust that referenced this pull request Aug 11, 2017
…main` method or in any other method returning a non-`std::io::Result` value fails, because of reasons mentioned in rust-lang/rfcs#1937.

I suggest uncommenting these parts of the examples, so that the examples are more "copy-pasteable" and show the true requirements for using them. The compilation errors I got wasn't enough to make me realize what the problem was:

```
error[E0277]: the trait bound `std::string::String: std::ops::Try` is not satisfied
```

(Thanks to the helpful people at #rust-beginners who helped me debug it; it was obvious once you knew the prerequisites for using the `?` operator.)
@nikomatsakis
Copy link
Contributor

Question for you @rust-lang/libs folks: the Termination trait is currently at the top-level (std::Termination). It feels like it should live in a module. The RFC doesn't seem to specify, unless I missed it.

Should it be std::termination::Termination? std::process::Termination?

@withoutboats
Copy link
Contributor

withoutboats commented Feb 22, 2018

I think process makes sense, its also where exit is.

Possibly ops, since it is sort of like operator overloading.

@Centril
Copy link
Contributor

Centril commented Feb 22, 2018

Personally I agree with the rationale by @withoutboats, process feels apt.

I also thought of ops, but the operator overloading comes from Try and it just happens to be the case that Result<T: Termination, E: Debug> is Try as well.. Termination does not seem general enough with respect to use cases to warrant inclusion in std::ops.

@nikomatsakis
Copy link
Contributor

ok.

@ExpHP
Copy link

ExpHP commented Feb 23, 2018

Since the built-in supported return types may be approaching stabilization, should the RFC be amended to reflect the current implementation and the reasoning behind them?

The implementation heading towards stabilization accepts E: Debug for Results, while the RFC uses E: Display.

@iddm
Copy link

iddm commented Nov 1, 2018

Guys, what prevents us from improving support of ? in the main so that it is possible to return Option as well?

Some would mean 0 (EXIT_SUCCESS) and None would mean 1 (or EXIT_FAILURE).

@scottmcm
Copy link
Member

scottmcm commented Nov 1, 2018

@vityafx Nothing technical, but Option<()> is rather a weird type. Do you have an example where using an option is better than just using .ok_or(something) to get a result?

@iddm
Copy link

iddm commented Nov 1, 2018

Simply if I have code in the main, I need a lot of ok_or things which is ugly and inconvenient

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-error-handling Proposals relating to error handling. A-expressions Term language related proposals & ideas A-syntax Syntax related proposals & ideas final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Return Result from main