Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upReturn `Result` from `main` #1176
Comments
This comment has been minimized.
This comment has been minimized.
|
I know @chris-morgan has thought about this a bunch and has some concerns |
This comment has been minimized.
This comment has been minimized.
|
I really remember a lot of discussion about this happening, but can’t find anything either, sadly. Off the top of my head some points raised during these discussions:
|
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
|
@nagisa Rust has overloading via traits, so this could work: trait MainReturn {
fn into_error_code(self) -> i32;
}
impl MainReturn for () {
fn into_error_code(self) -> i32 { 0 }
}
impl<E: Error> MainReturn for Result<(), E> {
fn into_error_code(self) -> i32 {
if let Err(e) = self {
write!(stderr(), "{}", e).unwrap();
1
} else { 0 }
}
}
#[lang="start"]
fn start<R, F>(argv: *const *const c_char, argc: u32, main: F) -> i32
where R: MainReturn, F: FnOnce() -> R
{
with_runtime(argv, argc, || {
main().into_error_code()
})
} |
This comment has been minimized.
This comment has been minimized.
daboross
commented
Jun 27, 2015
|
I'd argue that it is very easy and simple to just .unwrap() a Result returned from a fn main() {
try_main().unwrap();
}
fn try_main() -> Result<(), Box<Error> {
// ...
}To explicitly do this is just 4 extra lines of code - I would argue that adding this implicitly is just not worth it. |
This comment has been minimized.
This comment has been minimized.
|
@daboross Sorry, but did you read my second paragraph? That will print clutter the actual error with all the "thread main panicking at" business which is inappropriate for handling bad user input. It also sticks you with a single error code. |
This comment has been minimized.
This comment has been minimized.
daboross
commented
Jun 27, 2015
|
@Ericson2314 I guess it isn't entirely the best for some cases like bad user input - but I think it's totally appropriate for other errors which really are internal errors. One problem with having main() implicitly handle these errors is that it would be unclear what, if any, text would be included before/after the error. With error codes, it can really be very simply solved by a single It may turn out to be good to implement this, or not, I'm just here stating my opinion from my experience working with creating programs in rust. |
This comment has been minimized.
This comment has been minimized.
|
By returning a Result, a function is "kicking the can", effectively saying that the error is not it's fault. It follows then that any error error returned by main is the fault of whoever spawned the program and not the program itself. Sometimes internal errors get bubbled up a bit, but those should all be unwrapped at some point. I totally agree that trying that it is not obviously clear how the error in a result should be printed, but since this is for programming in the small, and since one can handle it themselves, I find it more tolerable than I would otherwise if libstd just picks something. |
This comment has been minimized.
This comment has been minimized.
yberreby
commented
Jul 1, 2015
|
I'd argue that adding 'convenient' special cases is not a very good idea in the long run; it increases complexity and coupling between Rust is not a scripting language. I think we should strive to avoid too much features designed for narrow use-cases bolted on it. Weren't ~ and @ removed because they could be implemented in libraries? |
This comment has been minimized.
This comment has been minimized.
ArtemGr
commented
Jul 1, 2015
|
When a function returns an error it thus handles the responsibility of doing something about the error to it's caller. For example, when a function that reads a file returns an error, the caller decides what to do about it, to print an error or to ask the user for another file name or to skip to the next file or to panic, etc. This chain of responsibility usually ends at Should we print a message or simply return a -1 error code to the OS? What about a GUI program, should it display a error message "dialogue"? Only the |
This comment has been minimized.
This comment has been minimized.
|
@filsmick There is already coupling between If the trait bounding the return type of |
This comment has been minimized.
This comment has been minimized.
BlacklightShining
commented
Sep 25, 2015
|
I agree with @ArtemGr. At the very least, there should be some platform-agnostic mechanism to cause the program to exit gracefully (unwinding stacks, dropping structs, etc) that could be used as a drop-in replacement for |
This comment has been minimized.
This comment has been minimized.
|
@BlacklightShining That could be achieved with a type wrapping the exit code and implementing |
This comment has been minimized.
This comment has been minimized.
droundy
commented
Dec 11, 2015
|
This sounds to me like a problem that is nicely handled by a utility function in the standard library, which could be named something like |
This comment has been minimized.
This comment has been minimized.
|
I'm not sure what's the pros over the current way
That pattern is redundant and can be reduced to |
This comment has been minimized.
This comment has been minimized.
ArtemGr
commented
Dec 11, 2015
|
A program usually returns an integer to the operating system. What integer to return and how to interpret it - Rust can not know this, only the program itself knows. (Even though there are certain conventions in UNIX, a program doesn't always have or can follow them). Callback passed to the If there's something else that needs to be done in case of the application failure, like displaying a dialogue, submitting a bug report or whatever, callback can do that as well. Though P.S. In fact, we could have a default callback that, for example, prints the error and returns 1 to the operating system, and calling |
This comment has been minimized.
This comment has been minimized.
No, they're very different.
I am executing something for its side effects, and it may error.
I have a thing that may or may not exist. In this case, the thing is an error. Option is not for result/failure, but for presence/absence. |
This comment has been minimized.
This comment has been minimized.
rphmeier
commented
Dec 11, 2015
|
The key question is what the error type should be. If this were anything but main, my initial reaction would be something like @eddyb's trait-based approach. However, it feels very strange to have a generic entry point. |
This comment has been minimized.
This comment has been minimized.
ArtemGr
commented
Dec 11, 2015
|
I wonder if |
This comment has been minimized.
This comment has been minimized.
|
@steveklabnik I see. |
This comment has been minimized.
This comment has been minimized.
|
@ArtemGr Oh don't get me wrong, this is shamelessly inflexible and opinionated for the sake of a little bit of convenience. @eddyb's trait idea's makes this just a bit less hard-coded and thus a bit less appalling. :), especially if the impls were in a separate crate (but then need new-type for coherence). |
This comment has been minimized.
This comment has been minimized.
Binero
commented
Jan 6, 2016
|
I like @eddyb's proposal, but would change the type depending on the platform. PlatformReturn could be defined in the std prologue and standardised values could be added to the std as well. This allows for a lot of flexibility. I would consider the only real negative side effect the verbosity of the main function. // PlatformReturn would be std::PosixReturn on Linux systems for example.
fn main<R: PlatformReturn>() -> R {
std::error::posix::Return::Ok
} |
This comment has been minimized.
This comment has been minimized.
BlacklightShining
commented
Jan 11, 2016
|
@Binero |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
Mar 29, 2016
|
This is becoming more complicated than it needs to be, everything understands return codes, main should just be; fn main() -> i32 {and whatever magic needs to happen to make the process really return that code. |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
Mar 29, 2016
|
It doesn't have to be i32, it can be a wrapper - but |
This comment has been minimized.
This comment has been minimized.
|
@ninjabear It can be as simple as |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
Mar 29, 2016
|
@eddyb - that's one of the better compromises I think, however nonzero results aren't necessarily errors - for example grep returns 1 if no lines were found - it's kind of up to the caller to decide if it's an error or not |
This comment has been minimized.
This comment has been minimized.
|
@ninjabear Sorry, I had forgotten the generalized approach I mentioned in my first comment here: // In libstd:
trait MainReturn {
fn into_error_code(self) -> i32;
}
impl MainReturn for () {
fn into_error_code(self) -> i32 { 0 }
}
impl<T: MainReturn, E: Error> MainReturn for Result<T, E> {
fn into_error_code(self) -> i32 {
match self {
Ok(x) => x.into_error_code()
Err(e) => {
write!(stderr(), "{}", e).unwrap();
101 // This is what panics, use, I think?
}
}
}
}// In your grep implementation:
enum GrepStatus {
Found = 0,
NotFound = 1
}
impl MainReturn for GrepStatus {
fn into_error_code(self) -> i32 { self as i32 }
}
fn main() -> io::Result<GrepStatus> {
// ...
let file = File::open(path)?;
// ...
Ok(status)
} |
This comment has been minimized.
This comment has been minimized.
|
I'd prefer using plain old integral return values, instead of results. These are simpler, and more expressive. |
This comment has been minimized.
This comment has been minimized.
BlacklightShining
commented
Apr 22, 2016
|
@ticki I'm going to have to disagree with you there. |
This comment has been minimized.
This comment has been minimized.
|
@ninjabear "single responsibility principle" is nothing but a corollary of "don't repeat yourself". It doesn't make sense to point a single function and ask if it's interface is repetitive. But yes, sloppy, unabstracted use of https://doc.rust-lang.org/std/process/fn.exit.html will quickly lead to repetition of magic numbers. |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
Apr 25, 2016
|
@Ericson2314 bringing this back on topic, given what you have just said - how is the function |
This comment has been minimized.
This comment has been minimized.
|
Again the problem is the redundancy in how its used. Either people abandon the |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
Apr 25, 2016
|
The redundancy in how what is used? |
This comment has been minimized.
This comment has been minimized.
ArtemGr
commented
Apr 26, 2016
•
I think what Ericson2314 means is that by implementing the integer codes in the errors (e.g. with something like a Suppose both the "foo" and the "bar" executables can return the |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
Apr 26, 2016
|
thanks @ArtemGr That idea is predicated on a misunderstanding of what SRP is (completely orthogonal to repeating yourself), cannot be reasonably implemented (there's a reason return codes aren't standardised by operating systems) and provides virtually zero benefits to users (how would I even know if I'm calling a Rust executable?) |
This comment has been minimized.
This comment has been minimized.
ArtemGr
commented
Apr 26, 2016
•
|
@ninjabear To a normal program, where exit codes are documented for and specific to a particular executable, binding Rust errors to integer error codes will only be a source of confusion and bewildered workarounds (essentially we'll keep doing the |
This comment has been minimized.
This comment has been minimized.
BlacklightShining
commented
Apr 30, 2016
|
@ninjabear You could use |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
May 4, 2016
|
So, consider an application that opens two files using stdlib. I use Even after I've returned the right You might say - that's cool we can make non |
This comment has been minimized.
This comment has been minimized.
BlacklightShining
commented
May 4, 2016
|
@ninjabear Okay, yeah, in those cases you'd have to use |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
May 4, 2016
|
There's a ton of other ways to remove magic numbers. Enums? Structs? Constants? All of these are available to you. I don't think there's anything further I can add to this thread. |
This comment has been minimized.
This comment has been minimized.
ArtemGr
commented
May 4, 2016
How is the "single impl" thing going to work? My guess was the opposite, that you'll have to implement the trait for every error type that It is much easier to track a single match block (wrapped in a function for reusability) than a dozen impls. |
This comment has been minimized.
This comment has been minimized.
BlacklightShining
commented
May 7, 2016
|
@ninjabear Enums sound good! Just one problem: you can't return an enum from @ArtemGr You make a new error type specific to your program, implement |
This comment has been minimized.
This comment has been minimized.
ninjabear
commented
May 8, 2016
enum ReturnValue {
Success = 0,
Failed = 1
}
fn main() -> i32 {
return ReturnValue::Success as i32
} |
This comment has been minimized.
This comment has been minimized.
Binero
commented
May 8, 2016
•
|
I just don't think the |
This comment has been minimized.
This comment has been minimized.
|
@Binero In pratice, that doesn't happen. |
This comment has been minimized.
This comment has been minimized.
|
Not to mention that |
This comment has been minimized.
This comment has been minimized.
norru
commented
May 19, 2016
|
Rust n00b here. What is the clean way to have a Rust program return a nonzero value to the operating system? I have read around that the recommended way is to run process::exit() but I seem to have read it doesn't run all the "destructor"/"cleanup" code. that doesn't sound very "clean". Is that right? |
This comment has been minimized.
This comment has been minimized.
|
@norru, well there are two possibilities:
use std::process;
fn start() {
// Program code here.
}
fn main() {
// We run the program.
start();
// Now that the function is over, the associated destructors
// (from variables and their children) are called, we can safely
// exit without getting into trouble:
process::exit(1);
}
This should be used when you need to show an error message to the user, or otherwise exit the program with a fail code. The former is prefered for internal error handling, the later for external error handling. |
This comment has been minimized.
This comment has been minimized.
norru
commented
May 19, 2016
•
|
So in the generic case it would look something like this in "pseudo-rust". Is this such an uncommon case in Rust? fn c_main(args: &Vec<String>) -> i32 {
if !valid_args(args) { return 2 }
// resource allocation RAII and other amenities
let context = init_stuff();
// do stuff with args
match context.do_something_with_args(args) {
Err(_) => return 1,
Ok(_) => return 0,
}
// context goes out of scope, gets dropped safely, all resources released etc
}
fn main() {
let args_vec: Vec<_> = env::args().collect();
let result = c_main(&args_vec);
process::exit(result);
} |
This comment has been minimized.
This comment has been minimized.
|
Well, that's fine, although I'd recommend to keep the whole body inside the secondary function, since that way you are less likely to commit logic errors. |
This comment has been minimized.
This comment has been minimized.
ActualizeInMaterial
commented
Jun 23, 2016
•
|
If main ends up returning exit codes, let's hope it doesn't do it like this(if this makes sense):
src: https://doc.rust-lang.org/std/process/fn.exit.html Oh wait, looks like the above comment is a good workaround. EDIT2: I guess I should've read this whole thread. My bad! buhuhuhu :D I'll kick myself out, :)) |
nrc
added
the
T-lang
label
Aug 29, 2016
This comment has been minimized.
This comment has been minimized.
|
I feel return codes here are a distraction. Don't try to make a return type handle your errors. If you need particular error handling, handle it inside main. The terse syntax is needed for code examples, and adding complex syntax and handling for it defeats the purpose. So I think it'd be perfectly fine if |
This comment has been minimized.
This comment has been minimized.
jaroslaw-weber
commented
Jul 12, 2017
|
i dont like the idea to change main signature but maybe it would be nice to have optional alternative. |
This comment has been minimized.
This comment has been minimized.
|
@jaroslaw-weber you might want to take a look at #1937 which is in it's Final Comment Period. |
Ericson2314 commentedJun 24, 2015
Rust's current error handling story is quite good for programming in the large. But for small programs, the overhead of matching the outermost
Result, printing the error, and returning the proper error code is quite tedious in comparison to temptation of just panicking.Furthermore, interactive tools shouldn't print a message implying the program is broken on bad manual input, yet such an "internal error" is exactly what the panic message applies. So there is no nice, succinct way to handle input errors with bubbling results or panics.
Allowing
mainto return a result would make our error handling story both compositional and succinct on programs on all sizes. Furthermore, should there be any platform-specific requirements/idioms for error-codes, etc, we can handle them transparently. While this does makemaina bit more complex, there is already a shim betweenstartandmainwith both C and Rust, so we're already down that rabbit hole.I was told by @eddyb this was already a wanted feature, but @steveklabnik didn't see an open issue so I made this.