rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.87k stars 12.51k forks source link

Tracking issue for RFC 1937: `?` in `main` #43301

Closed aturon closed 1 year ago

aturon commented 7 years ago

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

Steps:

Stabilizations:

Related issues:

Unresolved questions:

nikomatsakis commented 6 years ago

This is what I think:

Returning Result<T, E> should use the debug impl for E. This is the most convenient trait -- it's widely implemented, and satisfies the "quick and dirty output" use case as well as the unit testing use case. I'd prefer not to use Display both because it is less widely implemented and because it gives the impression that this is polished output, which I think is highly unlikely.

But there should also be a way to get professional looking output. Fortunately, there are already two such ways. First, as @withoutboats said, people can make a "display-from-debug" bridge if they want to use E: Display or otherwise give professional style output. But if this feels too hacky, then you can also just define your own type to replace result. e.g., you might do:

fn main() -> ProfessionalLookingResult {
    ...
}

and then implement Try for ProfessionalLookingResult. Then you can implement Terminate as well, whatever:

impl Terminate for ProfessionalLookingResult {
    fn report(self) -> i32 {
        ...
        eprintln!("Something very professional here.");
        return 1;
        ...
    }
}
withoutboats commented 6 years ago

I agree with @nikomatsakis that this should use Debug.

I also think that for polished output, writing some code in main is probably better than creating a new type to implement Try and Terminate. The point of Terminate seems to me to be for things libraries can easily make a good default for, which is just never the case for a sitution in which how the program terminates is significant to end users (e.g. professional CLIs).

Of course other people may have a different opinion, and there are multiple ways to use the traits involved to inject code instead of writing it directly in main. What's great is that we have multiple choices and we don't have to come up with a single blessed way to handle errors always.

nixpulvis commented 6 years ago

Let me just jot down some thoughts, though there are a few problems with them.

I'd love to see the following

fn main() -> i32 {
    1
}

Which could be written more generally as:

fn main() -> impl Display {
    1
}

Both of these main functions should return a 0 exit code and println! the Display of 1.

This should be as simple as the following (I'd think).

impl<T> Termination for T where T: Display {
    fn report(self) -> i32 {
        println!("{}", self);
        EXIT_SUCCESS
    }
}

Then for errors we can have:

impl<T: Termination, E: Debug> Termination for Result<T, E> { ... }

where the implementation is the same as in the RFC just with "{:?}" to use a Debug format.

As mentioned before, people who need more control over the output can simply write:

fn main() -> Result<i32, MyError> { ... }
impl Termination for Result<i32, MyError> { ... }

Though this would be undecidable with our current compiler I guess, since it would see conflicting implementations... So we do what @nikomatsakis suggests and write:

fn main() -> MyResult { ... }
impl Termination for MyResult { ... }
or, if you want something more general.
impl<T, E> Termination for MyResult<T, E> { ... }

I know this is partly restating things that have been said, but I thought I'd present my vision altogether, showing that a more general solution for displaying return values, not just results. Seems like a lot of this comment is arguing what implementations of Termination we ship by default. Also this comment is at odds with implementation like impl Termination for bool as described in the RFC. I personally think that non-zero exit codes should be handled exclusively by Results or custom types that implement Termination.

It's an interesting question of how to then handle ? on Option types in main since they don't have an implementation for Display.

U007D commented 6 years ago

TL;DR: I am OK with Debug.

More detailed explanation: I spent some time thinking about this yesterday and today with an aim to uncover implicit assumptions I have or am making to help arrive at the best possible answer.

Key Assumption: Of all the various types of applications that one can write in Rust, I think the console app stands to benefit the most/be most impacted by this decision. My thinking is that when writing a library, a AAA game title, an IDE or a proprietary control system, one would probably not be expecting a default main termination trait to meet one's need out of the box (indeed, one might not even have a main). So my bias toward Display as default comes from what we expect to see when using a small command-line application--for example:

$ cd foo
bash: cd: foo: No such file or directory

Most of us do not expect any sort of debugging aids, just a succinct indicator of what went wrong. I am simply advocating for this as a default position.

When I think of writing a Terminate impl to get simple output like this, I realize that the ? from main feature isn't that different from stable rust today (in terms of amount of code written), where a Result-aware "inner_main()" is often created in order to handle E.

With this assumption in mind, as a thought exercise, I tried to determine whether I felt strongly that the preponderance of "inner_main()"-style implementations in existence today were of a more-casual Display flavor (over a more-technical Debug flavor). My thinking was that this would be an indication of how the feature is likely to actually be used.

I was unable to convince myself that this is be the case. (Meaning, I don't think there currently exists a strong bias toward Display in existing implementations). Indeed, when looking through my own repositories that I've written over the past 16 months, I also found enough of both cases that I can't say that implementing Display by default would have amounted to a net savings.

Holding to the "primary beneficiary is the cli application" assumption, there are a great number of console apps which provide help and usage information. For example:

$ git foo
git: 'foo' is not a git command. See 'git --help'.

The most similar command is
    log

So even in for console apps, it's hard for me to identify an "injured group" by going with Debug.

And finally, I'd be happier with a Debug impl than with the feature held up for another 6 months, too, so, selfishly, there's that :).

So there's my thought process laid out in public. To summarize, I think Debug will no better and no worse than Display, and as such, should be fine as the default implementation.

Like many of you, I'm sure, I do wish there was an implementation I felt more excited about--like, "YES, THAT'S IT!!!", TBH. But maybe that's just my expectations being unrealistic... Maybe once we have a solution that works with failure cutting down boilerplate in my projects, it'll grow on me. :)

nikomatsakis commented 6 years ago

Note I opened a PR to support the terminatio trait in tests: #48143 (building on @bkchr's work).

I took the liberty of extending the Termination trait with a method for processing the result of the test. This simplified the implementation, but it also makes sense, since test failures may want more verbose output than executable failures.

withoutboats commented 6 years ago

Termination should be renamed Terminate after our general preference for verbs for traits in libstd.

jdahlstrom commented 6 years ago

@withoutboats I think at some point there was a discussion that verb traits are mostly those that have a single method with the same name as the trait. Anyhow, might I float again my own bikeshed suggestion, Exit?

SimonSapin commented 6 years ago

Gratuitous bikeshedding: this is a single-method trait. If we want to give them the same name, maybe ToExitCode / to_exit_code?

scottmcm commented 6 years ago

Stabilizing returning Result can be done independently from stabilizing the trait, right?

To me, this feels much like ? where most of the value comes from the language feature, and we can delay on figuring out the trait. The RFC discussion even made me wonder if the trait ever needs to be stabilized, since putting the code in an inner_main seemed easier than a trait impl for this...

withoutboats commented 6 years ago

Yes, we don't need to stabilize the trait - though it is useful on stable for things like frameworks, which can't necessarily rely so much on inner_main.

@SimonSapin I think of To as refering to a type conversion, and this doesn't. But we could name the method terminate (also I don't think this restriction on when to name traits verbs holds up. Try is an obvious counter example.)

nikomatsakis commented 6 years ago

I have proposed that we stabilize fn main() -> T where T is not unit. This leaves many details -- particularly the name/location/details of the trait -- unstabilized, but some things are fixed. Details here:

https://github.com/rust-lang/rust/issues/48453

Please give your feedback!

WiSaGaN commented 6 years ago

terminate seems to be more descriptive than report. We always terminate, but may omit reporting.

SimonSapin commented 6 years ago

But unlike for example std::process::exit this method does not terminate anything. It only converts the return value of main() into an exit code (after optionally printing Result::Err to stderr).

U007D commented 6 years ago

Another vote for Exit. I like that it is brief, quite descriptive and consistent with traditional concept of exit codes/exit status.

retep998 commented 6 years ago

I'd definitely prefer Exit over Terminate since we're gracefully exiting by returning from main, rather than suddenly hard terminating where we are because something went really wrong.

nikomatsakis commented 6 years ago

I added https://github.com/rust-lang/rust/issues/48854 to propose stabilizing unit tests that return results.

QuietMisdreavus commented 6 years ago

Oh hey, i found the proper place to talk about this.

Using ? in doctests

The way doctests currently work is something like this:

(I left a couple details out, but i conveniently made a full writeup here.)

So, to use ? seamlessly in a doctest, the bit that needs to change is the part where it adds fn main() { your_code_here(); } Declaring your own fn main() -> Result<(), Error> will work as soon as you can do that in regular code - rustdoc doesn't even need to be modified there. However, to make it work without declaring main manually will require a small tweak. Having not closely followed this feature, i'm not sure if there's a one-size-fits-all solution. Is fn main() -> impl Termination possible?

scottmcm commented 6 years ago

Is fn main() -> impl Termination possible?

In a shallow sense, yes: https://play.rust-lang.org/?gist=8e353379f77a546d152c9113414a88f7&version=nightly

Unfortunately, I think that -> impl Trait is fundamentally troublesome with ? due to the inbuilt error-conversion, which needs the inference context to tell it what type to use: https://play.rust-lang.org/?gist=23410fa4fa684710bc75e16f0714ec4b&version=nightly

Personally I was imagining ?-in-doctests working via something like https://github.com/rust-lang/rfcs/pull/2107 as fn main() -> Result<(), Box<Debug>> catch { your_code_here(); } (using syntax from https://github.com/rust-lang/rust/issues/41414#issuecomment-373985777).

The impl Trait version is cool, though, and might work if there was some sort of "prefer the same type if uncontrained" support that rustc could use internally in the ? desugar, but my recollection is that the idea of a feature like that tends to make the people who understand how things work recoil in horror :sweat_smile: But maybe it being an internal thing that only applies if the output is impl Trait would be feasible...

QuietMisdreavus commented 6 years ago

Ooh, those are very real concerns. I'd forgotten about how that would mess up type inference. If the catch block started Ok-wrapping like that linked issue, then that seems like a much easier (and much quicker, stabilization-wise) path forward.

The only thing i wonder is how that will be affected by the edition transition. Isn't the catch syntax changing in the 2018 epoch? Rustdoc will likely want to compile doctests in the same edition as the library it's running, so it would need to differentiate between the syntaxes based on the epoch flag it's been handed.

scottmcm commented 6 years ago

I'm concerned that this is now stabilized but simple cases apparently still ICE: https://github.com/rust-lang/rust/issues/48890#issuecomment-375952342

fn main() -> Result<(), &'static str> {
    Err("An error message for you")
}
assertion failed: !substs.has_erasable_regions(), librustc_trans_utils/symbol_names.rs:169:9

https://play.rust-lang.org/?gist=fe6ae28c67e7d3195a3731839d4aac84&version=nightly

frewsxcv commented 6 years ago

At what point do we say "this is too buggy and we should unstabilize"? Seems like if this hit the stable channel in its current form, it would cause a lot of confusion.

nikomatsakis commented 6 years ago

@frewsxcv I think the problems are fixed now, right?

U007D commented 6 years ago

@nikomatsakis the issue I raised in https://github.com/rust-lang/rust/issues/48389 is resolved in 1.26-beta, so yes from my perspective.

frewsxcv commented 6 years ago

Yep, the ICE I was worried about is fixed now!

BurntSushi commented 6 years ago

Apologies for chiming in at a point in which it's probably too late to do anything about this, but I wanted to leave my feedback here in case there was. I did read most of this thread, so I speak with that context in mind. However, this thread is long, so if it looks like I've overlooked something, then I probably did and I would appreciate having it pointed out to me. :-)

TL;DR - I think showing the Debug message of an error was a mistake, and that a better choice would be to use the Display message of an error.

At the core of my belief is that, as someone who routinely builds CLI programs in Rust, I can't ever remember caring much about what the Debug message of an Error is. Namely, the Debug of an error is, by design, for developers, not for end users. When you build a CLI program, its interface is fundamentally intended to be read by end users, so a Debug message has very little utility here. That is, if any CLI program I shipped to end users showed the debug representation of a Rust value in normal operation, then I would consider that a bug to be fixed. I generally think this should be true of every CLI program written in Rust, although I understand it may be a point on which reasonable people can disagree. With that said, a somewhat startling implication of my opinion is that we've effectively stabilized a feature where its default mode of operation starts you off with a bug (again, IMO) that should be fixed.

By showing the Debug representation of an error by default, I also think we are encouraging poor practice. In particular, it is very common in the course of writing a Rust CLI program to observe that even the Display impl of an error is not good enough to be consumed by end users, and that real work must be done to fix it. A concrete example of this is io::Error. Showing an io::Error without a corresponding file path (assuming it came from reading/writing/opening/creating a file) is basically a bug, because it is difficult for an end user to do anything with it. By electing to show the Debug representation of an error by default, we've made it harder for those sorts of bugs to be uncovered by folks creating CLI programs. (On top of that, the Debug of an io::Error is much less useful than its Display, but that on its own isn't a huge pain point in my experience.)

Finally, to round out my argument, I also have a hard time envisioning the circumstances under which I would use ?-in-main even in examples. Namely, I've been trying to write examples that match real world programs as closely as possible, and this generally has involved writing things like this:

use std::error::Error;
use std::process;

fn try_main() -> Result<(), Box<Error>> {
    // do stuff with `?`
}

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

On its face, it would be lovely to replace this with ?-in-main, but I can't, because it won't show the Display of an error. That is, when writing a real CLI program, I will in fact use the above approach, so if I want my examples to reflect reality, then I think I should show what I do in real programs and not take shortcuts (to a reasonable extent). I actually think this sort of thing is really important, and one side effect of this historically was that it showed folks how to write idiomatic Rust code without sprinkling unwrap everywhere. But if I revert to using ?-in-main in my examples, then I've just reneged on my goal: I'm now setting up folks who may not know any better to just write programs that, by default, emit very unhelpful error messages.

The pattern of "emit an error message and exit with an appropriate error code" is actually used in polished programs. For example, if ?-in-main used Display, then I could merge the main and run functions in ripgrep today:

https://github.com/BurntSushi/ripgrep/blob/64317bda9f497d66bbeffa71ae6328601167a5bd/src/main.rs#L56-L86

Of course, I could use ?-in-main in the future by providing my own impl for the Termination trait once that stabilizes, but why would I bother doing that if I could just write out the main function I have? And even so, this doesn't help with my conundrum when writing examples. I'd need to include that impl in the examples in order to make them match reality, and at that point, I might as well stick with the examples I have today (using a main and a try_main).

From the looks of it, it appears that fixing this would be a breaking change. That is, this code compiles on Rust stable today:

#[derive(Debug)]
struct OnlyDebug;

fn main() -> Result<(), OnlyDebug> {
    Err(OnlyDebug)
}

I think switching to Display would break this code. But I don't know for sure! If this is truly a ship-has-sailed issue, then I understand and there's not too much use in belaboring the point, but I do feel strongly enough about this to at least say something and see if I can't convince others and see if there's anything that can be done to fix it. (It's also quite possible that I am overreacting here, but I've so far had a couple people ask me "why aren't you using ?-in-main?" in my CSV examples, and my answer has basically been, "I don't see how it would ever be feasible to do that." Maybe that wasn't a problem that was intended to be solved by ?-in-main, but some folks certainly had that impression. With its current implementation, I could see it being useful in doc tests and unit tests, but I have a hard time thinking of other situations in which I would use it.)

XAMPPRocky commented 6 years ago

I agree that displaying the Display over the Debug is a better for CLI applications. I don't agree that that it should be Display instead of Debug, because that drastically limits what errors can actually be ?-ed and defeats the purpose of ?-in-main. As far I can tell(Could totally be wrong as I don't have the time to compile stdlib and I don't know if specialisation covers this) there's no reason we can't add the following impl to Termination. This would provide a non-breaking way to use Display when that is available and fall back to Debug when it doesn't.

#[unstable(feature = "termination_trait_lib", issue = "43301")]
impl<E: fmt::Display> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {}", err);
        ExitCode::FAILURE.report()
    }
}
mark-i-m commented 6 years ago

Finally, to round out my argument, I also have a hard time envisioning the circumstances under which I would use ?-in-main even in examples.

I have to agree with @BurntSushi in general for real CLI programs, but for random scripts and internal tools that only I plan to use, this is really convenient. Also, it's really convenient for prototypes and toys. We could always discourage actually using it in production code, right?

bstrie commented 6 years ago

Part of the goal with ?-in-main is to avoid unidiomatic code in code examples, namely unwraps. But if ?-in-main itself becomes unidiomatic, then that undermines the entire feature. I would strongly like to avoid any outcome that would cause us to have to discourage using it, in production code or otherwise.

WiSaGaN commented 6 years ago

I've been trying to use this feature in main and I've found it pretty frustrating. The existing impls of the termination trait just don't allow me to conveniently "accumulate" multiple kinds of errors -- for example, I can't use failure::Fail, because it doesn't implement Error; I can't use Box, same reason. I think we should prioritize changing to Debug. =)

If we use Display, we can introduce a fast and dirty type like MainError to accumulate multiple kinds of errors:

pub struct MainError {
    s: String,
}

impl std::fmt::Display for MainError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.s.fmt(f)
    }
}

impl<T> From<T> for MainError where T: std::error::Error {
    fn from(t: T) -> Self {
        MainError {
            s: t.to_string(),
        }
    }
}

This will allow something like below similar to Box<Error>:

fn main() -> Result<(), MainError> {
    let _ = std::fs::File::open("foo")?;
    Ok(())
}
scottmcm commented 6 years ago

Previous discussion of Display vs Debug is in the hidden part here, starting around https://github.com/rust-lang/rust/issues/43301#issuecomment-362020946.

Kixunil commented 6 years ago

@BurntSushi I agree with you. If this can't be fixed, there could be a workaround:

use std::fmt;
struct DisplayAsDebug<T: fmt::Display>(pub T);

impl<T: fmt::Display> Debug for DisplayAsDebug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl<T: fmt::Display> From<T> for DisplayAsDebug {
    fn from(val: T) -> Self {
        DisplayAsDebug(val)
    }
}

If this was in std::fmt, we could use something like this:

use std::{fmt, io};

fn main() -> Result<(), fmt::DisplayAsDebug<io::Error>> {
    let mut file = File::open("/some/file")?;
    // do something with file
}
zackw commented 6 years ago

I still don't have time to contribute any more to this for the foreseeable future, but I also agree that ? in main ought to be usable in serious CLI programs, and there were features in the original draft of the RFC that were intended to facilitate that. They got dropped in the name of incrementalism but maybe they should be revisited.

Boscop commented 6 years ago

I think it's fine to fix this in a backwards incompatible way if it's done soon, before much code out there is using it on stable.

emk commented 6 years ago

As somebody who writes a lot of CLI programs in Rust, and who's in charge of Rust style at work, I pretty much agree with @burntsushi here. I would have happily used the version of this feature specified in the RFC.

But I consider showing a Debug implementation to a user to be a bug, and not much better than just calling unwrap everywhere in a CLI program. So even in example code, I can't imagine ever using this version of the feature. This is too bad, because removing boilerplate from main would have simplified the learning for new Rust developers at work, and we wouldn't have needed to explain quick_main! or the equivalent any more.

Pretty much the only circumstances where I can imagine using this would be to eliminate unwrap from doctests. But I don't know if that's supported yet.

If we use Display, we can introduce a fast and dirty type like MainError to accumulate multiple kinds of errors:

Personally, this sort of workaround would add sufficient complexity that it's just easier to avoid ?-in-main completely.

@Aaronepower:

As far I can tell(Could totally be wrong as I don't have the time to compile stdlib and I don't know if specialisation covers this) there's no reason we can't add the following impl to Termination. This would provide a non-breaking way to use Display when that is available and fall back to Debug when it doesn't.

If this would enable me to write fn main() -> Result<(), failure::Error> and get a nice, human-readable error using Display, it would definitely satisfy my major concerns. However, I suppose that this would still leave the question of optionally displaying the backtrace in failure::Error when RUST_BACKTRACE=1 is set—but that might be out of scope, anyways, or at least an issue which should be taken up with failure.

nikomatsakis commented 6 years ago

This was stabilized some time ago, and I don't think we can really change the behavior of Result. I personally remain pretty happy with the current behavior for the use cases that I tend to do — quick and dirty scripts and the like — but I agree that more complex scripts won't want it. However, at the time of the discussion, we did also discuss a number of ways that you could control that behavior (I can't seem to find those comments now because github is hiding things from me).

For example, you could define your own "wrapper" for the error type, and implement From for it:

struct PrettyPrintedError { ... }
impl<E: Display> From<E> for PrettyPrintedError { }

impl Debug { /* .. invoke Display .. */ }

Now you can write something like this, which means you can use ? in main:

fn main() -> Result<(), PrettyPrintedError> { ... }

Perhaps such a type should be part of quick-cli or something?

BurntSushi commented 6 years ago

@nikomatsakis Yeah, I totally get that workaround, but I do feel like that defeats the purpose of the succinctness of using ?-in-main. I feel like "it's possible to use ?-in-main using this work-around" undermines ?-in-main itself unfortunately. For instance, I'm not going to write out your work-around in succinct examples and I'm not going to impose a dependency on quicli for every example I write either. I'm certainly not going to use it for quick programs either, because I want the Display output of an error in basically every CLI program I write. My opinion is that the debug output of an error is a bug in CLI programs that you put in front of users.

The alternative to ?-in-main is an additional ~4-line function. So if the workaround to fixing ?-in-main to use Display is much more than that, then I personally don't see much reason to use it at all (outside of doc tests or unit tests, as mentioned above).

mark-i-m commented 6 years ago

Is this something that could be changed in the edition?

Kixunil commented 6 years ago

@BurntSushi How would you feel about the workaround being in std, so you'd only have to write a bit longer return type declaration on main()?

BurntSushi commented 6 years ago

@Kixunil My initial feeling is that it might be palatable. Haven't given it too much thought though.

nikomatsakis commented 6 years ago

@BurntSushi

The alternative to ?-in-main is an additional ~4-line function.

Ultimately, the behavior is stable, and I don't think we can realistically change it at this point. (I mean it's not a soundness violation or something.)

That said, I still find the current setup pretty nice and preferable to Display, but I guess it's a matter of what you want the "default" to be — that is, either setup can be made to work like the other via various newtypes. So either we default to favoring "quick-n-dirty" scripts, which want Debug, or polished end products. I tend to think a polished end product is where I can afford an additional import readily enough, and also where I'd like to pick from many different possible formats (e.g., do I want just the error, or do I want to include argv[0], etc).

To be more concrete, I imagine it would look like this:

use failure::format::JustError;

fn main() -> Result<(), JustError> { .. }

or, to get a different format, perhaps I do this:

use failure::format::ProgramNameAndError;

fn main() -> Result<(), ProgramNameAndError> { .. }

Both of these feel reasonably nice compared to the 4-line function you had to write before:

use std::sys;

fn main() {
  match inner_main() {
    Ok(()) => { }
    Err(error) => {
      println!("{}", error);
      sys::exit(1);
    }
}

fn inner_main() -> Result<(), Error> {
  ...
}
nixpulvis commented 6 years ago

I'm coming around on this topic since, for production quality, adding what amouts to essensially a custom error type for the output seems like a good direction to push people anyway. This seems to paralell how panic!, for example, gives debug quality messages by default.

BurntSushi commented 6 years ago

@nikomatsakis That is perhaps a better long term view, and I do find your end result appetizing. It would be nice if those error types ended up in std some day so that they can be used in examples with as little overhead as possible. :-)

Process note: I tried to frame my initial feedback here with "is there anything that can be done" rather than "let's change something that has already stabilized," in an effort to focus on the high level goal rather than ask for something that we certainly cannot do. :-)

scottmcm commented 6 years ago

Well that didn't take long: https://crates.io/crates/exitfailure 😆

WiSaGaN commented 6 years ago

I am wondering how many people are surprised by using Debug trait versus Display trait. Both the initial discussion draft and the final RFC is using Display. This is similar to the universal impl trait incident, where people thought they are getting one thing, but only know they are getting another after it is getting stabalized. Also considering a lot of people who are surprised won't be using this feature, I feel there won't be big backslash if we change it in the next edition.

BurntSushi commented 6 years ago

@WiSaGaN The details of an RFC can and will change from time to time. This is expected and healthy. RFCs are design documents and not a perfect representation of what is and always will be. Information discovery is hard, and it's my fault for not paying more attention. I'm hoping that we can avoid relitigating the stability of this feature and instead focus on the higher level goals that we can feasibly act upon.

Kixunil commented 6 years ago

@nikomatsakis The main issue I see with favoring quick-n-dirty cases is that there already is a way to address them: .unwrap(). So from that perspective, ? is just another way to write quick-n-dirty things, yet there's no similarly easy way to write polished things.

Of course the ship has sailed, so we're mostly screwed. I'd love to see this changed in next edition, if possible.

nixpulvis commented 6 years ago

@Kixunil, unwrap is really not a good way to address things, as the title of this thread states we would like to be able to use the syntactic sugar ? in main. I can understand where you are coming from (in fact it was part of my initial hesitation with the use of ? for option handling) but with the inclusion of ? in the language this issue is a pretty nice usability win IMHO. If you need nicer output, then there are options for you. You don't release things to users without testing them first, and the discovery that you need a custom type for the output of main will be a pretty quick realization.

As for "why" we'd like ? in main, well consider how weird it is now. You can use it practically everywhere else (since you have control of the return type). The main function then ends up feeling kinda special, which it shouldn't.

Screwtapello commented 6 years ago

.unwrap() is a quick-and-dirty solution that doesn't compose, so you can't factor code in and out of main() while you're sketching out a program.

By comparison, ? is a quick-and-dirty solution that does compose, and making it not-quick-and-dirty is a matter of putting the correct return-type on main(), not of modifying the code itself.

Kixunil commented 6 years ago

@Screwtapello ah, now it makes sense to me. Thanks!

oblitum commented 6 years ago

I just want to express that I thought the aim of this was to bring ? in main for all applications without having to resort to additional wrappers... If it's just for testing I don't see much benefit in it and will keep sticking to .unwrap(), sadly.