ErrorFx: Fortran exception library

I think one has to ship two executables anyway, Debug mode and Release mode. I don’t know if there is a performance hit in Release mode for debugging symbols. But either way, if a program starts misbehaving, then running it in Debug mode is almost a must, to see if any array bounds are violated, etc. So as long as a beautiful stacktrace happens in Debug mode, that would be a good start.

I remember struggling with this with Intel compilers, that the bounds check didn’t give me a stacktrace, and it was very hard to figure out where it was coming from. However I thought they have some option to enable the stacktrace. GFortran does.

There are probably good reasons why statement expressions are only GNU extensions to C++ and did not become part of the standard language. :wink: Maybe, we should try first without them.

I think (I did not try it yet), we could do something like

@:assign_or_propagate(lhs, function_with_error(...), upgoing_error_var)

By far not as compact as

lhs = @:try(function_with_error(...))

but it would only need standard Fortran constructs. Unfortunately, the macro also needs to know the name of the variable, which will be used to propagate the error upwards.

Well, I always assumed this would become part of Fortran, such as a new try statement/expression.

We should try to design the functionality, and we can then try extending Fortran to also get nice syntax.

Given the current capabilities of Fortran and Fypp, an automatic error propagation upwards (as in the Serenity example), could look as follows:

Calling

@:declare_result_type(name=int_result, value_type=integer, error_type=type(fatal_error))

would generate the type

type :: int_result
  integer, allocatable :: value
  type(fatal_error), allocatable :: error
end type int_result

which then can be used to propagate the error “thrown” by the function. It works both, when the function call is embedded in an other function

function function_with_error(...) result(intres)
  type(int_res) :: intres

  integer :: ii

  @:try_assign(lhs=ii, rhs=function2_with_error(...),&
      & result_type=int_result, error=intres%error)
  print "(a,i0)", "Value obtained in the subroutine (no error thrown): ", ii
  intres%value = ii
  :

or in a subroutine:

subroutine subroutine_with_error(error)
  type(fatal_error), allocatable, intent(out) :: error

  integer :: ii
  type(int_result), allocatable :: ires

  @:try_assign(lhs=ii, rhs=function_with_error(fail=.true.),&
      & result_type=int_result, error=error)
  print "(a,i0)", "Value obtained in the subroutine (no error thrown): ", ii
  :

I’ve implemented and described that in a special branch of FxError, so it indeed works. However, to me it feels considerably less natural as in the case of the subroutines…

Cannot a compiler turn this:

ii = try function2_with_error(...)

into this:

  @:try_assign(lhs=ii, rhs=function2_with_error(...),&
      & result_type=int_result, error=intres%error)

?

Hm. I think, the tricky thing is the name of the error variable, which the containing scope uses to propagate the error upwards. If the containing scope is a function (and provided it returns itself a result-type), you just take the %error field of the return variable. But what do you do, if the containing scope is a subroutine? The error variable it uses to propagate the error upwards can be arbitrary (and one could even think about having more than one in the interface).

Probably, if the compiler would completely hide the error handling mechanism by extending the interface of the procedures automatically, it could also find out the names. Something like turning

subroutine with_error(...) throws(fatal_error)
end subroutine with_error

internally automatically into

subroutine with_error_internal(..., error)
  ...
  type(fatal_error), allocatable, intent(inout) :: error
end subroutine with_error

And similarly, turning

function with_error(...) result(something)
  sometype :: something

into

function with_error(...) result(res)
  type(result_sometype) :: res

with

type :: result_sometype
  sometype :: value
  type(fatal_error), allocatable :: error
end type result_sometype

(Actually, maybe even the subroutine should be turned internally into a function, so that the error handling is unique…)

1 Like

Right. Let’s think how a language extension could work, and I am happy to prototype it if you think you have a good idea to try. Just let me know.

Yes, probably we could indeed come up with a nice pattern, which allows to generate a similar code what we do with Fypp macros in ErrorFx. I’ve opened an issue on J3-Fortran for this.

1 Like

I have not read all of the conversation and I do not claim to have thought through all of the possible implications, but I wonder if the error variable has to be allocatable.

Well, the most important thing is, that the error variable is intent(out), so that we make sure the routine has a clear well defined error state (no error) at entry and we can catch cases, when an error returned by one call is passed without being handled to an other call. Now, if the error variable was static, it would have to be finalized whenever it is passed to a routine (because of the obligatory intent(out) attribute). On the other hand, if it is allocatable (and not allocated as it is the case as long as no error happened) no finalization would have to be done. So passing it into routines would involve considerably less overhead.

1 Like

Ah, that is the reason! Thanks :slight_smile:

1 Like