RFC Mentoring Opportunity: Permit `?` in `main()`

@nikomatsakis Can we just remove the main function? The entry point of Swift code execution is simply the main.swift file…

I’m not sure how implicitly wrapping the main.rs file in a main function would work with Rust’s module system, but it’s something to consider.

While looking through the RFC that @zackw posted, I kind of feel like it’s side-stepping a huge issue, which is what the regular “non-failure” return value needs to be. Look at this example from the RFC:

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

This looks great, but it only looks great because the last operation happens to be the stdout.flush(). Suppose that we wanted to do something else after:

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()?;
    some_method_call();
    Ok(())
}

Here we need to add that kind of ugly Ok(()) at the end of main. It’s not particularly intuitive why this needs to be there for someone new to Rust, and it’s also really subtle that it becomes unnecessary in the first example due to the omitted semicolon. Ideally this would “just work” without having to return anything special from main.

It might be worth considering if something like the original “checked exception” proposal isn’t more sensible. I think it was @glaebhoerl that first proposed this somewhere. Of course this is nothing like checked exceptions in Java, instead it’s just a little bit more syntax sugar on top of what we already have. The example above would become:

fn main() throws 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()?;
    some_method_call();
}

With the exact same semantics. That is the return value of main is still Result<(), io::Error>, just like before. However, the value that the function body evaluates to does not need to be (and perhaps cannot be) wrapped in a Result.

Together with some Trait and adding std::error::Error to the prelude this could become:

fn main() throws some 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()?;
    some_method_call();
}

That looks pretty nice and self-explanatory to me.

Perhaps this could be considered a completely orthogonal issue, but I thought I’d bring it up here anyway.

1 Like

Yes, I was wondering that, although I don't think it's really an alternative per se. That is, I think we still need the machinery that this RFC describes -- some way for main to offer more return values. Presumably this throws Foo stuff would be sugar that can be applied to any function, then.

1 Like

imo the Ok(()) issue is orthogonal to letting main() return non-() values, since that one currently affects all functions, while the ? issue is unique to main(). I also think “making ? work in main” is a huge win even without a solution for Ok(()).

But I do think allowing elision of Ok(()) the same way we allow eliding () today would also be an improvement. We should start a separate thread for that.

5 Likes

Thanks for all the feedback up to this point. I have made revisions, and opened an RFC pull request. A few specific responses:

  • Several people wanted to know more about what needed to be done to support ? in #[test] and doctests, so I've added a bunch more detail about that.

  • @scottmcm I was going back and forth on whether Err(()) should cause an unsuccessful exit. The Result<(),()> example has persuaded me it should.

  • @nikomatsakis Despite the above, I currently think we should not impl Termination for everything that is Display. This is a gut feeling and I don't have any specific argument for it. (I have removed the impl for bool as you suggested, and instead written a whole new section about how this feature can stay out of the way of people trying to conform to specific Unixy return value conventions, without having to put all those conventions into the stdlib.)

  • @jonysy "Just remove the main function" (and what, allow writing actual code at module scope?) would be an entirely different proposal with an entirely different set of issues to work through.

  • @jnicklas In my own code I have a whole lot of functions—not just main—with Ok(()) as their last line. I agree this is ugly, but I think addressing it is out of scope for this proposal. This is what I was trying to get at with the last line of the RFC:

    The ergonomics of ? in general would be improved by autowrapping all off-the-end return values in Ok if they're not already Results, but that's another proposal.

  • @alexcrichton I'm afraid I don't yet understand the guts of the runtime well enough to know what you meant about "updating the lang_start lang item." I tried to clarify the bit where I talked about compiler changes. I may be hung up on things that are not possible in C(++).

5 Likes

Ughhhhgh if this works in docs and tests you will be my hero !

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.