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
Specify Exit Code for exe #874
Comments
FreeBasic has an optional exit code to |
End statement is not the "clean style". So needing to use that to make an exit code is forcing people to apply this style. |
My thoughts: Option 1 is most explicit and flexible, allowing you to redefine the exit code multiple times. However this also makes it hard to track the ExitCode through the app if it can be set from anywhere. ExitCode is part of the exe's public API so should be as close as possible to the Entry Point routine. Option 2 is equally explicit but combines the ideas of exit code + raising an error to force the program to terminate, making it easier to identify from a traceback exactly what set the error code (since errors bubble). It still has the flexibility of redefining the error number by trapping it and re-raising a new exit_code. I think this makes it strictly better than Option 1. However there is this risk: Private Sub TriggerExit()
Err.Exit 123
End Sub
Private Sub BadSuppress() 'easy to suppress the exit error
On Error Resume Next
TriggerExit
On Error Goto 0 'clears Err.Number but not Err.ExitCode?
End Sub
Private Sub BadReThrow() 'easy to suppress the exit error
On Error Goto HandleError
TriggerExit
HandleError:
If Err.Number = 5 Then Exit Sub 'ignore this error
Err.Raise Err.Number, description:= Err.Description & "[unhandled]" 'rethrow last error with better error message
'BUG: we want Err.Exit not Err.Raise since the latter doesn't set the ExitCode
End Sub
Sub Main()
BadSuppress
BadReThrow
End Sub ... Although that can be circumvented with this pattern of only ever exiting from the top level of the program: 'The Main _Function_ takes the command line args and returns the exit code
Function Main(args... As Variant) As Long
On Error Goto ErrHappened
'do stuff
Return 0
ErrHappened:
Return exit_code
End Function
Sub Main() 'entry point
Err.Exit Main(args...) 'throws an error and sets the exit code
End Sub Python makes it a lot harder to suppress or redefine the
Option 3 I think is dumb - uncaught errors should be indicated with traceback to stderr/ a message box. However Option 3b of returning a 1 indicating an uncaught error is sensible IMO. I was very confused when my tB app had an error but returned 0 because I hadn't said otherwise! It should return 1 to make sure the caller doesn't just plough on unaware of the error. Option 3a may result in number collisions; e.g. I say my program will return exit code 5 to indicate "file not found", but then I get an uncaught "Invalid Procedure Call" bubble up which also sets the exit-code to 5, that would be bad. Option 4 I think is too implementation details-y level for VBA programmers, and introduces 2 kinds of Looking at tB's latest draft of design principles, I'd say
|
What does VB.Net use? Copy this :)) Main function is most natural IMO. Main sub vs Main function is a moot argument (not sure if allowed in TB) because one can have two Main subs overloaded too (if not mistaken) |
I don't think that someone could want to design the program to exit with the code of an unhandled error. |
I like more the |
@wqweto vb.net offers option 1 or option 4: i.e. a variable In fact Main has 4 possible overloads: Sub Main()
Sub Main(ByVal cmdArgs() As String)
Function Main() As Integer
Function Main(ByVal cmdArgs() As String) As Integer I don't know what would happen if you overload those with further arguments in .Net. Possibly a compiler error. Possibly you would just get a sub called Main but without the special behaviour of being the entry point. Certainly it would be a behaviour for a new user to learn rather than being obvious. Personally I'm not a fan of tB having an implicit entry point, I would prefer to be able to annotate any Sub as the entry point explicitly. But I understand this is a default to be consistent with VB6. That aside, I don't find the syntax that natural as you say. In VBx, we have a function to read the command line args - import sys
def main(args=None): # take optional args as Parm for unit testing
if args is None:
args = sys.argv() # read from command line
# continue
sys.exit(main()) But maybe that's just a consequence of python not having overloads... I don't know, I like the simplicity and lack of magic. On the subject of one vs many ways, vb.net also allows a third option. Private Declare Sub ExitProcess Lib "kernel32" (ByVal uExitCode As Long) And requires security permission to run unmanaged code. tB will always have the ability to call this method with dll declares, and I admit, don't really know the use case, something to do with multithreading. I don't think it should be the default because it exits without any cleanup. Maybe a wrapper can be added to the standard tB library when cross-platform becomes important. |
@EduardoVB Devil's advocate here (I also quite like option 2) - do you think it's sensible to use error propagation to terminate the program when VBx lacks particularly strong exception handling:
I'm just worried about the situation where the exit_code is set but then the Exit error is suppressed and then nobody remembers to reset the exit_code to 0, meaning the program terminates with non-zero exit code unintentionally. Maybe this can be done automatically, similar to how Err.Number gets cleared every so often. This is even more of a concern with option 1 IMO @Kr00l, how do you ensure users keep track of the current exit_code status throughout the application?
Now I think about it properly, I guess my problem is currently tB pops a useless "unhandled error" message box, rather than a traceback or even just the Err.Number. If tB included that then the exit code wouldn't also need to convey that information. That said, I think it's bad behaviour that at the moment even when there is an unhandled error , the exit code is set to 0. Python always sets it to 1 if there is an uncaught exception: > python -c "raise RuntimeError()"
Traceback (most recent call last):
File "<string>", line 1, in <module>
RuntimeError
> $LASTEXITCODE
1 Let's make a modification to Option 3: tB should by default return a non-zero exit code (let's say 1) if there was an unhandled error (as well as give a more detailed traceback or UpdateAdded 3a and 3b as new options in original post, to be used in combination with the other options. My leaning is Option 2 + Option 3b |
@Greedquest it's like with everything. It can be mis-used .. So, normally one would apply the exit_code on the last line of Sub Main.
Of course if someone uses I suggest using the |
@Kr00l I know, I'm just trying to think how to minimise misuse - i.e. is there a syntax that makes it harder to do so, rather than just enforcing by convention (using the pattern you describe). For example, Option 4, returning the exit code from the main function, it is unambiguous that the final point it is set is inside that function. Option 2, you can follow the error traceback to find the point that it was set (if we get a traceback). Using |
I'm gonna be quiet for a bit and open up the floor for discussion, I feel like I've said more than enough already! Don't want to monopolise the conversation 😅 |
If tB is not going to unload forms and terminate objects (and do other cleaning tasks to end the program normally), you can already exit with whatever code that you want by using the ExitProcess API. Do not use it in the IDE because it will close the IDE.
PS: I see it is already mentioned in the first post. |
TBH, I don't like the idea of
Which feels totally wrong. Because it'd be a global variable or a method called from somewhere, it feels messy. The option of returning a code from the main sub, IMO is more sensible because we can then control how the exitcode is assigned then potentially re-interpreted as it bubbles up through the callstack. I'm good with unhandled error returning as an exitcode, too. Not sure what is the VB6's behavior in this case if there's any back-compat issue. |
IFAIK VB6 doesn't return any kind of exit codes. |
Option 4 could simplified by having the compiler disallow overloads of Main - you can have a sub or a function as int, but not both. |
Humm, I thought that the option of Function Main was Option 2. Or whatever option is the one that makes Main to return the exit code. |
Is your feature request related to a problem? Please describe.
As well as stderr, stdout, exe's also specify an exit code - 0 indicating success, non-zero indicating failure. This is a very useful standard to integrate tB into other toolchains.
Describe the solution you'd like
Option 1:
A property for the ExitCode of the app (as opposed to an HResult of a function):
Err.ExitCode = exit_code 'sets exit code
Option 2:
A method to both set the exit code and raise an error that triggers the program to exit with normal error propagation
This is how Python does it, as it enables
with
statements to close cleanly, andfinally
blocks to run by default, no special casing in the language required.Option 3:
Instead of manually specifying the exit code, make the program exit with Err.Number. That way the last error raised is the exit code of the program.
Option 3a:
Return the Err.Number as exit_code as a sensible default for uncaught errors, but use one of the other options for manually specifying the exit code.
Option 3b:
As above, but always return 1 by default for uncaught errors. This is what python does:
Option 4:
We allow
Function Main() As Long
as the entry point instead ofSub Main()
- similar to C'sint Main()
. The return value is the exit codeDescribe alternatives you've considered
But that is unsafe. It means we can't run any cleanup as the process exits immediately. IDK if tB can even do proper COM cleanup of shared objects.
Python does include
os._exit([exit_code])
for immediate exit, butsys.exit([exit_code])
is preferred (this raises an untrappable exception to gradually exit the program but still run finally blocks). The former is only really used for child threads.As @wqweto points out, FreeBASIC has an optional parameter to the
End
statementhowever from the docs
Additional context
I want to consider how this fits with potential
try...catch...else...finally
blocks. twinbasic/lang-design#61 Particularly, a way to exit the program while still running finally blocks (and maybe evenClass_Terminate
, although that doesn't happen with classic VBx errors)The text was updated successfully, but these errors were encountered: