How will the exit status work for different -architectures- (replaced with platform)? Do I have to implement it for every -architecture- (platform) ?
@sebk This is not a CPU-dependent thing as far as I am aware.
Are you imagining contexts that do not provide an equivalent of the POSIX _exit
/ Windows ExitProcess
primitives, both of which take an integer exit status? (The only such case that comes to mind is Plan 9 with its string exit status.) I would suggest that such runtimes would supply their own, deliberately incompatible specification of the ToExitStatus
trait, in which exit_status
returns some other type or doesnât exist at all, and suitable #[cfg]
tags. Programs that use the stock impls in libstd do not need to change. Programs with their own impl will need to reimplement for the altered signature if they care about the exotic runtimes in question.
@zackw architecture wasnât the correct term there. Maybe platform is better.
I mean the mapping of return code to âsemantic meaningâ (how it is handled) is not uniform between Linux, Windows, etc.
Would every Err value create the same exit status, or would there be different values for, say OutOfMemory, File::read failed and DNS lookup failure?
If they all share the same numerical value, (assuming Ok => 0, Err => 1 for platform X), do we even need ToExitStatus?
@sebk Unixy systems mostly map all âfailureâ exits to exit code 1, or sometimes 2 (as with grep
, where code 1 means âno matches foundâ). There have been a few attempts to standardize a richer set of meanings (e.g. BSD sysexits.h
) but mostly no one bothers with them. I donât have enough experience with other platforms to talk about them.
If there is a genuinely widely used convention for mapping system errors to exit statuses on Windows (for instance), then Rust ought to conform - but that would be a separate thing from ToExitStatus
. The point of ToExitStatus
is that the application can define what it means by a Result
returned from main
, with a couple of stock options. The mapping from io::Error
values to platform-specific process exit statuses is properly io::Error
's business.
I do get the idea, but I am not sure ToExitStatus
is the optimal solution.
type OsResult = Result<(), OsError>;
fn run<R: Into<OsResult>() -> R;
The above would work too, except one would implement Into<OsError>
for the different Error types.
I certainly donât mean to be saying that I think ToExitStatus
is optimal, it was just the first thing I thought of.
Maybe we should back up a bit. As I see it, the design goals here are, in decreasing order of importance:
- The
?
operator should be usable inmain
. - Existing code with
fn main() -> ()
should keep working, but itâs ok (IMHO) to have to change the signature ofmain
if you want to use?
inside, and maybe the empty return value can be deprecated. - When
?
is used in main, its effects should be consistent 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 written tostderr
when a program fails due to a system error. - Since we are messing with
main
anyway, letâs make it easier to program to those platform conventions; right now itâs a little clumsy. - 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. (I do not like how, at present, âall errors are panicsâ is the very easiest thing, and âthe program returns exit code 0 whether or not an error occurredâ is the second easiest thing.)
Can we agree on these, before we go back to talking about how to implement?
I do agree on 1, 2, 3 and 5. How â 4 can be solved is unclear to me.
Also in my opinion a simple main()
signature would be great.
The following might cause confusion, as the return type is not clear.
fn main() -> impl Terminate {
Ok(2+2)
}
Another idea, that this example shows is the ability to return anything that implements Display.
I believe it is important to ensure that user isnât required to use some new signature for the main.
Other than that i believe alternative signature e.g. Result<(), Err>
should be provided by user if he wants to return some error.
This error most likely should be able to provide some short error description which would go to stderr and optional exit code (if there is no exit code then we could just return any non-zero) which is used if possible for target. Preferably that any kind of error would be acceptable without much of user intervention to wrap some API errors. I.e. just use accept object with std::error:Error? Maybe introduction of optional ExitCode trait for Error (is it possible to have optional trait?) if user wish to customize return code, but it must be up to compiler to decide how this code is propagated to system.
How about some nifty macro like that:
#[result_to_exit]
fn main() {
let mut f = File::open("foo")?;
f.write_all("Hello, world!")?;
}
That would take the main
body and make it main_inner
and handle the return value.
This way it could be implemented as a standalone crate, we could see what community thinks, and if well received, it could be added to std
one day, maybe.
I do like this approach. Aside from the syntax, and not yet supporting a derive macro, that is basically what quick_main from error_chain does.
I think the main point of the RFC is - or should be - that simple
examples donât use unwrap
, have per default a sensible error
handling with the aid of ?
and that beginners can use the
signature of main
as an example how they can write their first
own functions.
Therefore I would strongly prefer if the result value of main
would be named after Result
, that beginners see this type/name quite
early and get a first idea about it.
I would love if beginners would just see something like:
fn main() -> Result {
...
}
If they later see things like Result<u32>
or Result<u32, SomeError>
,
they might already think: oh, this result returns a u32
, and this
other one has some special kind of error.
One caveat with this pattern is that you also need an explicit return value at the end. You can't just let it fall off the end of the function with ()
anymore -- it has to be Ok(())
. In this case you could have just let the write_all
result be returned, but you have to remember to do this.
IOW, once you opt into returning errors in some form, you also have to be more explicit about the non-error case. Maybe we could implicitly use Into
on return values, then From<T> for Result<T,E>
would handle this case.
Argh, yeah. I hate that. I always feel a bit goofy writing Ok(())
. Sometimes I wish we had adopted @glaebhoerl's full proposal, including the throws
keyword.
If catch
were implemented, you could do something like
fn main() -> io::Result<()> {
catch {
...
}
}
but at that point we're getting pretty close to this prior suggestion by @Nemo157, which I had somewhat discounted at the time but was perhaps prescient:
fn main() {
catch {
let mut core = Core::new()?;
...
}.unwrap();
}
What if returned values of type T
got implicitly turned into Ok(T)
in functions that return a Result<T,_>
? Then you wouldnât need a catch
in main
, and youâd also get some nice ergonomic benefits in all the functions that return a Result
. No more manually wrapping âsuccessâ values in Ok
, including those goofy Ok(())
. I canât really think of any downsides.
Iâve thought about it. In practice it would be a kind of coercion. We could probably add it in a backwards compatible way with a bit of effort â though in closures it would be harder, since sometimes we have to infer the return type there. I imagine ultimately the biggest concern is that currentl Result
is not otherwise special to the language itself (though obviously it plays a big role in the libraries).
Just posing the question:
Would it be bad to automatically coerce into any enum
variant on return
, if unambiguous â not just into Ok(..)
of Result
?
Doing it for any enum sounds like it could result in some very odd things. SeekFrom
would magically coerce from u64
but not i64
, for example. It could be a strange stability trap too, for people adding variants.
FWIW, I donât think catch
is needed for the case above, since thereâs no additional return
scope. It could just be spelled (stably!) like this:
fn main() -> io::Result<()> {
Ok({
let mut f = File::open("foo")?;
f.write_all("Hello, world!".as_bytes())?;
})
}
I guess itâs too late to give any method body containing a ?
that wrapper automatically. Error returns could be Err(e)?
, giving essentially throws
without needing the keyword.
I think by allowing coercion or conversion (through From
or something else):
- from
T
toResult<T, !>
- from
E: Error
to!
(with panicking) thenResult<T, !>
either automatically or semi-automatically(with some syntax sugar), then allowing user to specify either ()
or Result<(), !>
as main()
's return value, this issue might be solved?
Besides that I like the catch {}.unwrap()
idea from @Nemo157 too, hopefully auto generated with an annotation on main().
Panic isnât necessarily the right behavior, though. See what I said back up in #57 and #59.
@zackw Iâd rather think âexit statusâ is too much OS-dependent so not portable enough.
Thereâre always two exits for main()
: returning and unwinding. Returning from main() does not necessarily mean that the program wonât panic at a later stage. It an âexit statusâ is actually needed, i think people always need to turn uncatched panic into defined âexit statusâ code, which canât be avoided at all. Providing a code as main()
's return value means that an upcoming panic shall either overwrite it or supplement it, both of which is more complex than people would expect and not obvious. Instead, i think people can just setup to make the global handler of uncatched panics to return such a value.