fortrangfortranintel-fortranfortran2003

Are Fortran's "final" subroutines reliable enough for practical use?


Modern Fortran contains various object-oriented ideas, including a concepts of "destructors" through the FINAL keyword.

MODULE mobject
  TYPE :: tobject
    ! Data declarations
  CONTAINS
    FINAL :: finalize
  END TYPE
CONTAINS
  SUBROUTINE finalize(object)
    TYPE(tobject) object
    ...
  END SUBROUTINE
END MODULE

However, is this feature reliable? Notably, I noticed inconsistencies about when, and whether at all, it will be called, with major differences between Intel Fortran 19 and GFortan 7, 8:

I noticed no difference between gfortran-7.4.0 and gfortran-8.2.1.2.

These inconsistencies raise some questions about the practical usability of destructors for me. Is either of the behaviors fully conforming with the standard? Is the standard unclear on this? Does the standard maybe contain clauses, that lead to unintuitive behavior?

Detailed analysis (code see below)

This means, that the programmer has to explicitly check, if the instance has been initialized.


Testing Code

!! -- Makefile ---------------------------------------------------
!! Runs the code with various compilers.

SHELL = bash
FC = NO_COMPILER_SPECIFIED
COMPILERS = gfortran-7 gfortran-8 ifort
PR = @echo$(n)pr -m -t -w 100

define n


endef

all: 
    rm -rf *.mod *.bin
    $(foreach FC, $(COMPILERS), $(n)\
      rm -rf *.mod && \
      $(FC) destructor.f90 -o $(FC).bin && \
      chmod +x $(FC).bin)
    $(PR) $(foreach FC, $(COMPILERS), <(head -1 <($(FC) --version)))
    $(info)
    $(foreach N,0 1 2 3 4 5 6,$(n) \
      $(PR) $(foreach FC, $(COMPILERS), <(./$(FC).bin $(N))))



!! -- destructor.f90 ---------------------------------------------

module mobject
  implicit none
  private
  public tobject, newObject

  type :: tobject
     character(32) :: name = "<undef>"
   contains
     procedure :: init
     final :: finalize
  end type tobject

contains

  subroutine init(object, name)
    class(tobject), intent(inout) :: object
    character(*), intent(in) :: name
    print *, "+ ", name
    object%name = name
  end subroutine init

  function newObject(name)
    type(tobject) :: newObject
    character(*), intent(in) :: name
    call new%init(name)
  end function newObject

  subroutine finalize(object)
    type(tobject) :: object
    print *, "- ", object%name
  end subroutine finalize

end module mobject



module mrun
  use mobject
  implicit none
contains

  subroutine run1()
    type(tobject) :: o1_uninit, o2_field_assigned, o3_tobject, o4_new, o6_init
    type(tobject), allocatable :: o5_new_alloc, o7_init_alloc
    print *, ">>>>> run1"
    o2_field_assigned%name = "o2_field_assigned"
    o3_tobject = tobject("o3_tobject")
    o4_new = newObject("o4_new")
    o5_new_alloc = newObject("o5_new_alloc")
    call o6_init%init("o6_init")
    allocate(o7_init_alloc)
    call o7_init_alloc%init("o7_init_alloc")
    print *, "<<<<< run1"
  end subroutine run1

  subroutine run2Array()
    type(tobject) :: objects(4)
    print *, ">>>>> run2Array"
    objects(1)%name = "objects(1)_uninit"
    objects(2) = tobject("objects(2)_tobject")
    objects(3) = newObject("objects(3)_new")
    call objects(4)%init("objects(4)_init")
    print *, "<<<<< run2Array"
  end subroutine run2Array

  subroutine run3AllocArr()
    type(tobject), allocatable :: objects(:)
    print *, ">>>>> run3AllocArr"
    allocate(objects(4))
    objects(1)%name = "objects(1)_uninit"
    objects(2) = tobject("objects(2)_tobject")
    objects(3) = newObject("objects(3)_new")
    call objects(4)%init("objects(4)_init")
    print *, "<<<<< run3AllocArr"
  end subroutine run3AllocArr

  subroutine run4AllocArrAssgn()
    type(tobject), allocatable :: objects(:)
    print *, ">>>>> run4AllocArrAssgn"
    objects = [ &
         tobject("objects(1)_tobject"), &
         newObject("objects(2)_new") ]
    print *, "<<<<< run4AllocArrAssgn"
  end subroutine run4AllocArrAssgn

  subroutine run5MoveAlloc()
    type(tobject), allocatable :: o_alloc
    print *, ">>>>> run5MoveAlloc"
    o_alloc = getAlloc()
    print *, "<<<<< run5MoveAlloc"
  end subroutine run5MoveAlloc

  function getAlloc() result(object)
    type(tobject), allocatable :: object
    print *, ">>>>> getAlloc"
    allocate(object)
    object = newObject("o_alloc")
    print *, "<<<<< getAlloc"
  end function getAlloc

  subroutine run6MovePtr()
    type(tobject), pointer :: o_pointer
    print *, ">>>>> run6MovePtr"
    o_pointer => getPtr()
    deallocate(o_pointer)
    print *, "<<<<< run6MovePtr"
  end subroutine run6MovePtr

  function getPtr() result(object)
    type(tobject), pointer :: object
    print *, ">>>>> getPtr"
    allocate(object)
    object = newObject("o_pointer")
    print *, "<<<<< getPtr"
  end function getPtr

end module mrun



program main
  use mobject
  use mrun
  implicit none
  type(tobject) :: object
  character(1) :: argument

  print *, ">>>>> main"
  call get_command_argument(1, argument)
  select case (argument)
  case("1")
     call run1()
  case("2")
     call run2Array()
  case("3")
     call run3AllocArr()
  case("4")
     call run4AllocArrAssgn()
  case("5")
     call run5MoveAlloc()
  case("6")
     call run6MovePtr()
  case("0")
     print *, "####################";
     print *, ">>>>> runDirectlyInMain"
     object = newObject("object_in_main")
     print *, "<<<<< runDirectlyInMain"
  case default
     print *, "Incorrect commandline argument"
  end select
  print *, "<<<<< main"
end program main

Output of the testing code

>> make
rm -rf *.mod *.bin
rm -rf *.mod && gfortran-7 destructor.f90 -o gfortran-7.bin && chmod +x gfortran-7.bin  
rm -rf *.mod && gfortran-8 destructor.f90 -o gfortran-8.bin && chmod +x gfortran-8.bin  
rm -rf *.mod && ifort destructor.f90 -o ifort.bin && chmod +x ifort.bin

pr -m -t -w 100  <(head -1 <(gfortran-7 --version))  <(head -1 <(gfortran-8 --version))  <(head -1 <(ifort --version))
GNU Fortran (SUSE Linux) 7.4.0   GNU Fortran (SUSE Linux) 8.2.1 2 ifort (IFORT) 19.0.4.243 2019041

pr -m -t -w 100  <(./gfortran-7.bin 0)  <(./gfortran-8.bin 0)  <(./ifort.bin 0) 
 >>>>> main                       >>>>> main                       >>>>> main
 ####################             ####################             ####################
 >>>>> runDirectlyInMain          >>>>> runDirectlyInMain          >>>>> runDirectlyInMain
 + object_in_main                 + object_in_main                 + object_in_main
 <<<<< runDirectlyInMain          <<<<< runDirectlyInMain          - <undef>
 <<<<< main                       <<<<< main                       - object_in_main
                                                                   <<<<< runDirectlyInMain
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 1)  <(./gfortran-8.bin 1)  <(./ifort.bin 1) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run1                       >>>>> run1                       >>>>> run1
 + o4_new                         + o4_new                         - <undef>
 + o5_new_alloc                   + o5_new_alloc                   + o4_new
 + o6_init                        + o6_init                        - <undef>
 + o7_init_alloc                  + o7_init_alloc                  - o4_new
 <<<<< run1                       <<<<< run1                       + o5_new_alloc
 - o7_init_alloc                  - o7_init_alloc                  - o5_new_alloc
 - o6_init                        - o6_init                        + o6_init
 - o5_new_alloc                   - o5_new_alloc                   + o7_init_alloc
 - o4_new                         - o4_new                         <<<<< run1
 - o3_tobject                     - o3_tobject                     - <undef>
 - o2_field_assigned              - o2_field_assigned              - o2_field_assigned
 <<<<< main                       <<<<< main                       - o3_tobject
                                                                   - o4_new
                                                                   - o6_init
                                                                   - o5_new_alloc
                                                                   - o7_init_alloc
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 2)  <(./gfortran-8.bin 2)  <(./ifort.bin 2) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run2Array                  >>>>> run2Array                  >>>>> run2Array
 + objects(3)_new                 + objects(3)_new                 - <undef>
 + objects(4)_init                + objects(4)_init                + objects(3)_new
 <<<<< run2Array                  <<<<< run2Array                  - <undef>
 <<<<< main                       <<<<< main                       - objects(3)_new
                                                                   + objects(4)_init
                                                                   <<<<< run2Array
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 3)  <(./gfortran-8.bin 3)  <(./ifort.bin 3) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run3AllocArr               >>>>> run3AllocArr               >>>>> run3AllocArr
 + objects(3)_new                 + objects(3)_new                 - <undef>
 + objects(4)_init                + objects(4)_init                + objects(3)_new
 <<<<< run3AllocArr               <<<<< run3AllocArr               - <undef>
 <<<<< main                       <<<<< main                       - objects(3)_new
                                                                   + objects(4)_init
                                                                   <<<<< run3AllocArr
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 4)  <(./gfortran-8.bin 4)  <(./ifort.bin 4) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run4AllocArrAssgn          >>>>> run4AllocArrAssgn          >>>>> run4AllocArrAssgn
 + objects(2)_new                 + objects(2)_new                 + objects(2)_new
 <<<<< run4AllocArrAssgn          <<<<< run4AllocArrAssgn          - objects(2)_new
 <<<<< main                       <<<<< main                       <<<<< run4AllocArrAssgn
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 5)  <(./gfortran-8.bin 5)  <(./ifort.bin 5) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run5MoveAlloc              >>>>> run5MoveAlloc              >>>>> run5MoveAlloc
 >>>>> getAlloc                   >>>>> getAlloc                   >>>>> getAlloc
 + o_alloc                        + o_alloc                        + o_alloc
 <<<<< getAlloc                   <<<<< getAlloc                   - `4�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
 <<<<< run5MoveAlloc              <<<<< run5MoveAlloc              - o_alloc
 - o_alloc                        - o_alloc                        <<<<< getAlloc
 <<<<< main                       <<<<< main                       - o_alloc
                                                                   <<<<< run5MoveAlloc
                                                                   - o_alloc
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 6)  <(./gfortran-8.bin 6)  <(./ifort.bin 6)
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run6MovePtr                >>>>> run6MovePtr                >>>>> run6MovePtr
 >>>>> getPtr                     >>>>> getPtr                     >>>>> getPtr
 + o_pointer                      + o_pointer                      + o_pointer
 <<<<< getPtr                     <<<<< getPtr                     - `��\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
 - o_pointer                      - o_pointer                      - o_pointer
 <<<<< run6MovePtr                <<<<< run6MovePtr                <<<<< getPtr
 <<<<< main                       <<<<< main                       - o_pointer
                                                                   <<<<< run6MovePtr
                                                                   <<<<< main

Solution

  • TLDR: There are known outstanding issues in Gfortran. Intel claims full support. Some compilers claim no support.


    The question about reliability and usability in general is quite subjective, because one has to consider many points that are unique to you (Do you need to support multiple compilers? Do you need to support their older versions? Which ones exactly? How critical it is if some entity is not finalized?).

    You pose some claims which are hard to answer without actual code examples and may be a topic for a separate complete questions and answers. Gfortran publishes the current status of implementation of Fortran 2003 and 2008 features in this bug report https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37336 (the link points to a meta-bug that points to several individual issues tracked in the bugzilla). It is known and that the feature is not finished and that there are outstanding issues. Most notably (at least for me), function results are not being finalized. An overview of the status of other compilers (simplified to Y/N/paritally) is at http://fortranwiki.org/fortran/show/Fortran+2003+status and used to be periodically updated in Fortran Forum articles.

    I can't speak about those alleged spurious finalizations of Intel Fortran. If you identified a bug in your compiler, you should file a bug report with your vendor. Intel is generally quite responsive.

    Some individual issues could be answered, though. You will likely find separate Q/As about them. But:

    If you want any of the points to be answered in detail, you really have to ask a specific question. This one is too broad for that. The help/instructions for this site contain "Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the [ask] for help clarifying this question."