-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return Result
from main
#1176
Comments
I know @chris-morgan has thought about this a bunch and has some concerns |
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:
|
|
@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()
})
} |
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. |
@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. |
@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. |
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. |
👎 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? |
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 |
@filsmick There is already coupling between If the trait bounding the return type of |
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 |
@BlacklightShining That could be achieved with a type wrapping the exit code and implementing |
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 |
I'm not sure what's the pros over the current way
That pattern is redundant and can be reduced to |
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 |
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. |
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. |
I wonder if |
@steveklabnik I see. |
@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). |
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
} |
@Binero |
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. |
It doesn't have to be i32, it can be a wrapper - but |
@ninjabear It can be as simple as |
@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 |
@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)
} |
I'd prefer using plain old integral return values, instead of results. These are simpler, and more expressive. |
@ticki I'm going to have to disagree with you there. |
Again the problem is the redundancy in how its used. Either people abandon the |
The redundancy in how what is used? |
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 |
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?) |
@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 |
@ninjabear You could use |
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 |
@ninjabear Okay, yeah, in those cases you'd have to use |
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. |
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. |
@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 |
enum ReturnValue {
Success = 0,
Failed = 1
}
fn main() -> i32 {
return ReturnValue::Success as i32
} |
I just don't think the |
@Binero In pratice, that doesn't happen. |
Not to mention that |
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? |
@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. |
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);
} |
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. |
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, :)) |
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 |
i dont like the idea to change main signature but maybe it would be nice to have optional alternative. |
@jaroslaw-weber you might want to take a look at #1937 which is in it's Final Comment Period. |
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
main
to 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 makemain
a bit more complex, there is already a shim betweenstart
andmain
with 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.
The text was updated successfully, but these errors were encountered: