Skip to content

Safety functions for working with allocatables #317

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

Open
awvwgk opened this issue Feb 10, 2021 · 3 comments
Open

Safety functions for working with allocatables #317

awvwgk opened this issue Feb 10, 2021 · 3 comments
Labels
topic: utilities containers, strings, files, OS/environment integration, unit testing, assertions, logging, ...

Comments

@awvwgk
Copy link
Member

awvwgk commented Feb 10, 2021

Apologies in advance as this topic is still a bit fuzzy on my side.

The general idea is to provide some kind of safety functions to work with allocatable variables, the most commonly used is the deferred length character variable. There is one minor annoyance, an empty deferred length character can either be an empty string or unallocated, but usually we would like it to behave consistently for this case, i.e. len(dlc) returning zero instead of a segmentation fault when the dlc variable is not allocated.

Adding the required additional checks is usually quite redundant, therefore providing a good and general mechanism to deal with such situations might be in scope for stdlib.

For the particular case of a deferred length character the function might look like (note that this function will create a copy of the character variable in the progress of making it safe):

!> Safely return a deferred length character
elemental function maybe(string) result(maybe_string)
  character(len=:), allocatable, intent(in) :: string
  character(len=maybe_len(string)) :: maybe_string
  if (allocated(string)) then
    maybe_string = string
  else
    maybe_string = ''
  end if
end function maybe

!> Safely return the length of a deferred length character
elemental function maybe_len(string) result(length)
  character(len=:), allocatable, intent(in) :: string
  integer :: length
  length = merge(len(string), 0, allocated(string))
end function maybe

Similar functions could be defined for allocatable integers, reals and logicals if we can agree on a “neutral” element for those data types.

The naming is loosely oriented at Rust's maybe, ok and err functionality, emulating something like this in stdlib would be the ultimate goal of the proposal.

@awvwgk awvwgk added the topic: utilities containers, strings, files, OS/environment integration, unit testing, assertions, logging, ... label Feb 10, 2021
@certik
Copy link
Member

certik commented Feb 10, 2021

I think this would be useful. Note that the function can only be pure, but not elemental in this case.

@ivan-pi
Copy link
Member

ivan-pi commented Feb 10, 2021

The current implementation of fpm relies quite heavily on deferred-length character strings. Perhaps trying to use maybe in a few cases can help inform a specification. The file dependency.f90 contains many instanced of allocated.

I see some similarity with the optval function with the inquiry function present() replaced by allocated(). The default argument value is ''.

I can imagine maybe would be handy to join the elements in the newly proposed string list type (#311).

@ivan-pi
Copy link
Member

ivan-pi commented Feb 11, 2021

A related concept using associate, modeled upon std::option in Rust:

module option_mod

  implicit none

  type :: option_t
    integer, allocatable :: some
  end type

contains

  function positive_int(value) result(res)
    integer, intent(in) :: value
    type(option_t) :: res

    if (value > 0) then
      allocate(res%some)
      res%some = value
    end if
  end function

  logical function is_some(opt)
    type(option_t), intent(in) :: opt

    if (allocated(opt%some)) then
      is_some = .true.
    else
      is_some = .false.
    end if
  end function

  logical function is_none(opt)
    type(option_t), intent(in) :: opt
    is_none = .not. is_some(opt)
  end function

  integer function some(opt)
    type(option_t), intent(in) :: opt
    some = opt%some
  end function

end module

program main

  use option_mod

  implicit none

  associate(res1 => positive_int(4), res2 => positive_int(-3))

    call process(res1)
    call process(res2)

  end associate

contains

  subroutine process(res)
    type(option_t), intent(in) :: res

    if (is_some(res)) then
      associate(x => some(res))
        print *, "Result: ", x
      end associate
    else
      print *, "Value was not positive"
    end if
  end subroutine

end program

With ifort I get the output:

Result:            4
Value was not positive

With gfortran I get the spurious error:

option_mod.f90:52:0:

   52 |     call process(res2)
      | 
Warning: ‘res1’ may be used uninitialized in this function [-Wmaybe-uninitialized]

and the program crashes due to a segmentation fault.

Perhaps this approach makes sense, when there is no "default" value to be used -> None.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: utilities containers, strings, files, OS/environment integration, unit testing, assertions, logging, ...
Projects
None yet
Development

No branches or pull requests

3 participants