j3-fortran / fortran_proposals

Proposals for the Fortran Standard Committee
175 stars 14 forks source link

Yet another error-handling proposal #304

Open jwmwalrus opened 1 year ago

jwmwalrus commented 1 year ago

I'be been looking at some of the proposals (here) for error-handling in Fortran, and even though they're good, none of them seem to take advantage of existing Fortran idioms, but rather borrow heavily from other language(s).

One of the things I like in Fortran 2023 is the enumeration type ---which would be perfect, had it included the option for a formatted type-bound write.

Using that enumeration type as a guideline, an exception could be declared as:

module my_exceptions
    implicit none
    private

    exceptions type, public :: io_ex
        exception :: open_error, read_error
        exception :: write_error
    contains
        write(formatted) :: write_io_ex    !<-- there is no need for generic here
    end exceptions type

    exceptions type, public :: print_only
        exception :: to_error_unit
        exception :: to_log
    end exceptions type

contains
    subroutine write_io_ex(dtv, unit, iotype, v_list, iostat, iomsg)
        ...
        select case (dtv)
        case (open_error)
            write (unit, '(...)') 'error opening unit...'
        end select
    end subroutine
end module my_exceptions

With that, I think subroutines (and only a subroutines i.m.o.) could declare and "throw" exceptions like this:

module my_io
    use my_exceptions
    implicit none
contains
    subroutine sub(arg) failing with(io_ex, print_only)    !<-- annotate expected failures
        ...
        open (NEWUNIT = unit, IOSTAT = ios, ...)
        if (ios /= 0) fail with (open_error)    !<-- throw exception here
        ...
        close (unit, IOSTAT = ios)
        if (ios /= 0) fail with (to_error_unit)    !<-- thrown at the end, so failure could be ignored
    end subroutine
end module my_io

And another program unit calling the subroutine could do this:

program test
    use my_io
    use iso_fortran_env
    implicit none

    exception :: handle_ex    !<-- define the variable "catching" the exception

    call sub(arg) with (handle_ex)    !<-- without the "with (handle_ex)", the failure would propagate up the stack

    ! handling alternative one: handle by exception
    select case (handle_ex)
    case (open_error)
        print *, handle_ex
    case (to_error_unit)
        write (ERROR_UNIT, *) handle_ex    !<-- error, no formatted type-bound write
    case default
        ...
    end select

    ! alternative 2: handle by type
    select type (handle_ex)
    type is (io_ex)
        ...
    type is (print_only)
        ...
    class default
        ...
    end select
end program test

With that, the exception handling facilities could be defined by leveraging existing features, avoiding reliance on inheritance, and not having to deal with explicit error codes.

There might be some pending details though, like

And although I think a try-catch block would be unnecessary with the above, for syntactic-sugar purposes it could be something like

exception :: catcher
...
failing [ with (catcher) ]              !<-- the "with (catcher)" is optional
    call sub(arg) 
recover from (open_error, print_only)
    select case (catcher)               !<-- only possible if catcher is explicit for the failing block
    ...
    end select

    select type (catcher)               !<-- only possible if catcher is explicit for the failing block
    ...
    end select
recover from (write_error)
    ...
recover all
    ...
end failing

So, any thoughts on the above?