-
Notifications
You must be signed in to change notification settings - Fork 16
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
Exceptions in Fortran #236
Comments
Actually, if we wanted to omit the central error type and take out inheritance from the game, we could just allow the user to declare arbitrary types (with some constraints, like no pointers in them) as "throwable" without having them to derive from a base class, e.g. type, throwable :: io_error
character(:), allocatable :: filename
integer :: unit = -1
end type io_error That would be probably even more "fortranic". Maybe then try catch (errorvarname)
...
catch all
! things to do, if the error type is irrelevant.
! As we don't know what the type is, no
! access to errorvarname is allowed here
end try catch |
@aradi thanks for opening up an issue for this to start a discussion. So the single line
It greatly simplifies error handling, but it is just nicer syntax for what can be done already, as you have shown with your ErrorFx library. For reference, to make this proposal standalone, the proposal is that this code: subroutine routine_propagating_error(..., error)
...
type(fatal_error), allocatable, intent(out) :: error
...
! If error happend, we propagate it upwards, otherwise we continue
try call routine_with_possible_error(...)
print "(a)", "Apparently no error occured"
...
end subroutine routine_propagating_error is equivalent to: subroutine routine_propagating_error(..., error)
...
type(fatal_error), allocatable, intent(out) :: error
...
call routine_with_possible_error(..., error)
! If error happend, we propagate it upwards, otherwise we continue
if (allocated(error)) return
print "(a)", "Apparently no error occured"
...
end subroutine routine_propagating_error There is no "magic" behind the scene. Can you write what this code is equivalent to: try catch (errorvar)
i = function_with_error
! or altenatively
call subroutine_with_error()
catch (io_error)
! do something
print *, "UNIT:", errorvar%unit
! We pass it upwards as a more generic error
throw fatal_error :: errorvar
catch (some_other_error)
! do something else for this error
end try catch I think we have not discussed this part yet. |
@certik To ease the communication and discussion, I've created the FException repository which contains the new syntax for all cases I've considered so far (throwing error, propagating error, catching error, error in functions) and also the equivalent Fortran 2008 code. (They can be compiled and executed). I've slightly changed the syntax for the assignment to try i = function_with_error(...) to be more similar to the A
would be substituted with (see catch_exception.f90)
Of course, apart of transforming the new syntax into the equivalent Fortran 2008 code, the compiler will need to check, whether all possible exceptions, which the code in the |
I see, nice. I didn't know you can exit from a "block". What is nice about this approach to exception handling is that there are no extra features needed in the compiler "backend", no long jumps, no stack unwinding, no magic. All this is is just a nicer syntax, checked by the compiler's "frontend", but underneath using current Fortran features, so regarding questions like performance there is no additional overhead over what you would need to write by hand anyway. @aradi, regarding |
Finally, exceptions have been discussed a lot in this repository. Here are other relevant threads: #6, #66, #172. See this C++ paper about a See this thread in J3 about approaches to exceptions for Fortran: In particular, a "status" variable: Copying the relevant part here: To make it clear what I am talking about, here is an example code in current Fortran: https://gist.github.com/certik/bd8235d2e1d049b22fcd1016f4914430 Here is my original "try" proposal: https://gist.github.com/certik/d950b7468228ff6f7d8bbc680a0943a7 and here is the new "status" proposal: https://gist.github.com/certik/2293270ac39f2589ae702ed751f405f8 In particular, the "original try proposal" is very similar to your "try" above. It doesn't throw allocatable derived type, but just an integer, although I think it probably can be extended to other types. But it's the same idea. The "status proposal" adds a The main objection to the "status" proposal from the J3 thread above is that
I think that is true that whatever we propose does not directly map into every possible error handling mechanism that a library might use. I think the idea of the above proposal (and its variations) is that it allows a simple solid mechanism to handle errors in Fortran that people can use, but don't have to. If they choose to use it, it should work for almost any use case, but one must adapt the library a bit. Perhaps gradually. |
I think, the try res = function_throwing_error1(ierror)&
& + function_throwing_error2(ierror) has the Fortran equivalent try: block
integer :: tmpres1, tmpres2
tmpres1 = function_throwing_error1(ierror, internal_errorvar)
if (allocated(internal_errorvar)) return
tmpres2 = function_throwing_error2(ierror, internal_errorvar)
if (allocated(internal_errorvar)) return
res = tmpres1 + tmpres2
end block try (See also funcexpr_exception.f90 and funcexpr_exception_fxy.f90.) The biggest problem I have with this variable extending approach, that we would be not allowed to use the exception mechanism in |
Yes, that's exactly how it would work. I don't see a problem from the implementation perspective. I don't know if there can be some issues with the Fortran standard, if we are breaking some rule about Fortran's expression evaluation.
Why cannot the function by |
IIRC, |
As for the status-proposal: I think, our current approach is superior. There is no scenario, where a program would be error stopped due to an exception. Rather, we would force the programmer to either handle the exception or propagate it upwards. Code, which does not do any of the two (e.g. which tries to ignore an exception) would be simply invalid. If the programmer wanted to stop the code due to an exception, it would have to happen explicitly, e.g.
All intrinsic commands/routines (
The exception |
Yes, I like this overall design so far. That's where a compiler comes in, to enforce that the error is handled one way or another. The way it is enforced in your library currently is that it will fail at runtime when the error object gets deallocated, which is the only way I think it can be done currently, but if it is part of the language, then it will get enforced at compile time, which I think is much better. |
@certik In case, you want to prototype it in LFortran, I've found a blog entry about the error handling implementation in Swift. I think, this lightweight solution should be exactly, what we should be heading for. Actually, thinking about Swifts strategy, we could also consider to use a simple subroutine subroutine_with_error(...) throws
...
throw io_error(name="somefile.dat")
...
end subroutine subroutine_with_error This would then simplify interface declarations as well, as one would not have to list all the errors explicitly. And we should go with the Swift convention, that an routine, which does not throw an error, matches an interface, which allows for error throwing (but not the other way around). We should then probably also require, that every Last, but not least, we could also easily mimic Switfs ! some_value contains either the return value of function_throwing_error (if no error occured) or -1 (if error occured)
some_value = try function_throwing_error() else -1 And the some_value = try_stop function_throwing_error() The ! Would stop the code, if the subroutine has thrown an error
try_stop call subroutine_throwing_error() |
I think the Swift's approach is essentially like Rusts or Zigs, except that Swift handles this automatically, so the error is not exposee explicitly as a type, but the compiler does the equivalent thing: it stores the error as a return type (in a special register) and checks it in the caller. That is a design that we can also pursue -- just add some keywords like Notice that all these designs (Rust, Zig, Swift) have one thing in common: no long jump, no stack unwinding like traditional C++ exceptions. Just syntax sugar for returning the error as a result of a function or subroutine. They differ how the error is returned (whether explicitly, or implicitly). |
Based on the discussion on fortran-lang.discourse about the ErrorFx library, a mechanism for exception like error handling should be created.
ErrorFx demonstrates, that such a mechanism is possible with current Fortran already. But it needs a lot of boiler plate code, which in ErrorFx are currently substituted by Fypp-macros. I drafted a possible syntax for all the scenarios we can already handle in ErrorFx. I'd also provide equivalent Fortran code, but probably we can already start discussion, in case somebody sees some general flaws in the concept.
The text was updated successfully, but these errors were encountered: