Open bclothier opened 2 years ago
Yes, this is an interesting area. In vbWatchdog for VBA/VB6 there was of course no option to add real block-scoping to the catch/finally blocks, but we do potentially have the option to change that in the tB version.
When we do get to this, one of my main concerns is the introduction of new keywords and the clashes that would entail. If we introduce real Try
, Catch
, and Finally
keywords, these are likely to clash with some existing codebases, but if we stick to the current vbWatchdog options of ErrEx.Catch
, ErrEx.Finally
(and now ErrEx.Try
), then they are much less likely to. Or we could stick them on Err
instead... Err.Try
, Err.Catch
, Err.Finally
:
Public Sub DoSomething()
Err.Try
Debug.Print 1/0
Err.Catch 11
MsgBox "division by zero..."
Err.CatchAll
MsgBox "unexpected error..."
Err.Finally
MsgBox "cleaning up..."
End Sub
Just ideas. There's plenty of time to discuss on this.
Err.Try is of course weird because it doesn't look like a keyword, but Debug.Print sets a bit of a precedent in this regard, it's a slightly special method.
I prefer sorry got confused between Err.NoError
or Err.NoException
(if we get structured exceptions) to Err.Finally
- finally has always felt a bit weird to me because it sounds like it should run after everything, exception or not.
Python has try... except...else
, kinda like Case Else
but that's a bit too vague imo and case else matches everything not captured by a previous block in VBA, whereas the NoError
label is more like Catch 0
and uncaught errors get bubbled up.finally
and else
, thanks @bclothier!
You could make it a two word keyword like With Try
With Catch
. Or make it a contextual keyword:
Sub foo()
Try
result = 1/0
Catch 11
MsgBox "division by zero..."
CatchAll
MsgBox "unexpected error..."
NoError
Debug.Print "Answer was: "; result
Finally
MsgBox "cleaning up..."
End Try
'[...]
End Sub
I.e. Try
is only considered a reserved keyword (rather than a parameterless sub call) when matched by an End Try
. Within that block Catch
is also a keyword - it won't break existing code because it's only a keyword in the context of the try block.
This is how the new python match block works - they are only keywords in an unambiguous context, otherwise they can be used as normal.
finally has always felt a bit weird to me because it sounds like it should run after everything, exception or not.
That's exactly the intention and is important especially in an unmanaged language like twinBASIC where we need to guarantee cleanup to avoid worse problems before leaving a routine, regardless whether it succeeded or had an error. An "NoErr" that only runs when there was no error wouldn't be very useful.
In traditional VBx, I consider it actually errorprone to set up a proper cleanup. To illustrate:
Public Sub DoThings()
On Error GoTo ErrHandler
If Precondition = False Then
Exit Sub 'OKish
End If
Dim HotHandle As LongPtr
HotHandle = OpenHandle(...) 'watch out!
If Some Condition = False Then
Exit Sub 'Bad! The handle is still hot!
End If
If OtherCondition = False Then
GoTo ExitProc 'Better, we close the handle
End If
'Some more code that could potentially raise an error...
ExitProc:
'We must release the handle before leaving
If HotHandle Then CloseHandle(HotHandle)
Exit Sub
ErrHandler:
If Err.Number = X Then
GoTo ExitProc 'Bad! Error still active
ElseIf Err.Number = Y Then
Debug.Print "oh, well!"
'Bad, no resume and we fall out without cleaning up
Else
Resume ExitProc 'OK
End If
End Sub
This code shows that we have to juggle between Exit Sub
, GoTo <label>
, Resume <label>
, depending on where we are in the code, and using them in wrong places will cause problems at runtime. I don't think that's a very desirable way of ensuring that the HotHandle
will have a CloseHandle
called on it when leaving the method. More importantly, the CloseHandle
is not a part of the error handling which further confuses the intention of what we need to do in this method. We just need it to happen no matter whether there was an error or not to avoid leaking a handle.
As one possible alternative to help separate the cleanup from the error handling is to allow something like this:
Dim HotHandle As LongPtr On Release CloseHandle(HotHandle)
But honestly I'm a bit iffy on this because the cleanup might require more complex logic (in the example above, we used a single-line If
statement to test whether HotHandle
is nonzero before calling CloseHandle
on it.
One way to enhance the language without adding new keywords would be something like:
On Return Resume ExitProc
or
On Error Or Resume Resume ExitProc
This way, whether you use Exit Sub
, Exit Function
, Resume <label>
, or Return <some value>
, it will run the label regardless. The downside is that it's using a label rather than a block and things could get weird with multiple labels or multiple On...Resume
statements. Also, one still can fall through into that block unintentionally.
For the record, I'm fine with either Err.Try
or ErrEx.Try
to avoid adding keywords if the proposed Try
block is not an option.
Just as a FYI, 'On' is another possible choice:
On Try
On Catch
On Finally
@bclothier Ah, my mistake, in python they have the else
and finally
and I think they're both useful actually (updated the other comment):
def divide(x, y):
try: # get the resource/value
result = x / y
except ZeroDivisionError: # can't get the resource
print("division by zero!")
else: # use the resource and clean it up
print("result is", result)
finally: # cleanup that runs in all situations
print("executing finally clause")
I don't think I've really used finally
, just else
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print("division by zero!")
else:
print("result is", result)
print("executing finally clause")
else/NoError
is perfect when opening a resource might make an error, and so you only need to close the resource if the try block suceeded:
Dim token As Long = RemovePassword("C:/file.csv") 'remove password to access file
Try
Dim file As CSVFile = New CSVFile("C:/file.csv")
Catch ERR_OPEN_FILE 'this block only runs if there was an error opening the file
Debug.Print "Could not open file, maybe it is readonly"
NoError 'this block only runs if we were successful opening the file
Dim fileContents As String = file.readAll
file.close
Finally
RestorePassword token, "C:/file.csv" 'always restore password, regardless of whether file was openable
End Try
Debug.Print "Finished!"
Apparently finally is useful when you do an early Return
(see here) as you mention On Return Resume CloseHandle
Is there any possibility of elevating twinBasic above the try catch paradigm. WOuld it be possible to allow the definition of a local procedure which is called when the surrounding scope ends.
E.g. from @bclothiers example would it be possible to have something like a Termination procedure defined for specific variables within the 'DoThings' method.
Def Terminate HotHandle()
'Do the stuff necessary to correctly close HotHandle
end Terminate.
@bclothier Re: comment https://github.com/twinbasic/lang-design/issues/61
In traditional VBx, I consider it actually errorprone to set up a proper cleanup. To illustrate:
FWIW we actually use gosub/return to sort of mimic a "finally" block. The pattern is like this:
Function DoSomething() As Boolean
On Error Goto ErrHandler
...
' All exit points either Goto ExitFunction explicitly or just reach that point naturally
...
ExitFunction:
GoSub Cleanup
Exit Function
Cleanup:
' Close files, connections, whatever is needed
Return
ErrHandler:
TemporarilyStoreError ' Prevents Err details from being lost due to any cleanup operations
GoSub Cleanup
LogError ' records stored Err details in a pseudo-stack trace then raises it again, so the calling function's error handler gets triggered... etc. A bit like exception stack unwinding.
End Function
(With MZTools it was not hard to add this to code semi-automatically. Otherwise its hard to maintain)
Is your feature request related to a problem? Please describe. twinBASIC provides
Return
which allow us to leave the function immediately. However, there is no way to guarantee proper clean up. In other languages, this is usually managed with aTry
/Finally
block. I know that it is planned to integrate vbWatchDog which does provide bothErrEx.Catch
andErrEx.Finally
but noTry
keyword.Considering that there is also an open request for block scoping ( twinbasic/lang-design#33 ), that may be one way to help make it easier to write code that won't be so deeply arrowed due to mixing of error handling & control flow & cleaning.
Describe the solution you'd like At a minimum, implement the equivalent of
Finally
orErrEx.Finally
to make it easy to define a block of code that must be run no matter what.Describe alternatives you've considered We still can achieve similar thing using traditional error handling, but instead of doing
Exit Sub
, we'd need to useGoTo CleanUp
orGoTo ExitProc
to guarantee the cleanup on an early exit, and be careful to useResume CleanUp
when in an error handler block which isn't all that great for language consistency.