Alternate main() signatures


#1

As a follow-up to this Reddit thread (but I’ve seen similar complaints several times in other places), how do people feel about supporting alternate signatures for main()? I realize, this is only useful for short examples and such, but first impressions are important.

So, what if we allowed using any of the following?: fn main() fn main() -> Result<(),u32> fn main() -> MainResult // same as above but with a type alias

maybe also these: fn main(args: env::Args) -> MainResult fn main(args: env::ArgsOs) -> MainResult

As a less invasive middle-ground, I suppose we could also create an attribute macro that wraps body of the function in a closure as suggested in this comment: #[unwrap_result] fn main()


#2

:-1:

I rather would see more usage of and_then and map instead of this.

What I want is sane and stable way to set exit code status.


#3

I think having one alternate of the return type fn main() -> u32 is enough.

Isnt there no life after main, therefore we cant really use Rust types as return stuff? Or am I completly missing the point of life after main?

I like the args input though.


#4

I think having one alternate of the return type fn main() -> u32 is enough.

The idea was to enable usage of try!() in main().

Isnt there no life after main, therefore we cant really use Rust types as return stuff? Or am I completly missing the point of life after main?

It just means that Rust does not have static constructors/destructors. There absolutely is life between the actual process entry point and main().


#5

Youre correct on all counts, but it still feels strange. I feel like there is a better solution, this just seems like a bandaid (in regards to returning result just to use try!)


#6

FWIW, this can be simulated via an immediately invoked closure:

fn main() {
    (|| {
        ...
    })().unwrap()
}

One doesn’t need to write out any explicit types (as compared to a separate function), but it does look quite strange.


#7

Yes, that’s what I meant. However, we’d have to explain to developers what this magic macro does, and, IMO, it would look very much like a band-aid.

Hey, here’s another idea: Can we restructure try! and FromError such that the return type no longer has to be specifically a Result, and impl FromError for ()? Something like this:

macro_rules! try {
    ($expr:expr) => (match $expr {
        $crate::result::Result::Ok(val) => val,
        $crate::result::Result::Err(err) => {
            //was: return $crate::result::Result::Err(
            //                            $crate::error::FromError::from_error(err))
            return $crate::error::FromError::from_error(err)
        }
    })
}

// back-compat with existing code
impl<T,E,F> FromError<F> for Result<T,E> where E:FromError<F> {
    fn from_error(err:F) -> Self {
        std::result::Result::Err(FromError::from_error(err))
    }
}

impl<E> FromError<E> for () where E:Error {
    fn from_error(err: E) -> () {
        panic!(err.description().to_string());
    }
}

Unfortunately, right now ‘impl<T,E,F> FromError<F> for Result<T,E>…’ above conflicts with ‘impl<E> FromError<E> for E’ from std. I wonder if negative trait impls would help here? I.e. could we re-define the latter as ‘impl<E> FromError<E> for E where E:!FromError<E>’ ?


#8
(|| {
    ...
}())

No dogballs please!! :stuck_out_tongue:


#9

You would want to use a different trait than FromError for that conversion, although the current name isn’t very clear about what it’s producing (it’s technically ErrorFromError or ErrorToError or ErrorConvert), and so it’s hard to find a good name that’s not FromError. That said, try! sometimes failing doesn’t seem like great API design - the function author may have merely forgotten to specify a Result return type and instead of a nice type error, the function could panic in production.