Trick for optional arguments with defaults

It’s very common when dealing with optional arguments to have a local variable that is assigned either a default value or the value of a present argument, like:

subroutine bar(...,a)
    real, intent(in), optional :: a
    real :: a_local
    a_local = 2.0; if (present(a)) a_local = a
    ... ! use only a_local in the further code
end subroutine

And it’s also very common to use the argument a by mistake instead of a_local, resulting in a segmentation violation during execution if a was not present.

A way to prevent such an error is to use an ASSOCIATE block that spans the whole routine:

subroutine bar(...,a)
    real, intent(in), optional :: a
    real :: a_local
    a_local = 2.0; if (present(a)) a_local = a
    ASSOCIATE( a => a_local )
    ... ! "a" can now be safely used as an alias to a_local
    END ASSOCIATE
end subroutine

The dummy argument a is shadowed by the alias a inside the block. Another benefit is that we don’t have to use the local variable name with a somehow artificial suffix (_local or whatever)

13 Likes

I only just started using blockend block in things like loops to declare local variables and such in a smaller scope.

Can you nest these structures? Meaning can you have multiple associate or block constructs within each other?

In theory, you can have as many nested associate and block constructs as you want.

In practice:

The associate-construct is a thin pseudo-scope (the underlying behavior is that of pointers and shadowing), so you could nest as many as you want… until it starts getting confusing.

The block-construct, on the other hand, is a true scope, so it’s bound by the stack’s limits (and recursion complicates things).

Both constructs are also bound by the compiler’s rules (e.g., for ifort, I think the magic nesting number was 31, all constructs combined).

We also created the optval function in stdlib exactly for this purpose.

4 Likes

The two approaches are actually complementary. optval() is fine as long as the argument is used once or twice. If repeatedly used it will tend to be less readable than a local variable (and a change of the default value requires several changes in the code).

They can also be combined (not tested, but should work):

ASSOCIATE( a => optval( a, 2.0 ) )
2 Likes

ASSOCIATE blocks work, more or less, like invoking a contained procedure with actual arguments being associated with the contained procedure dummy arguments. However, one difference is that ASSOCIATE blocks are allowed to be nested arbitrarily by the standard (at least to some compiler limit), while CONTAINS is limited to only one level (or two, if you count the CONTAINS statement in a module). A programmer runs into that CONTAINS limit when converting statement functions in legacy codes to contained procedures within a module. That limit has always annoyed me, I guess for being so arbitrary and seemingly unnecessary. You can usually work around the problem by flattening the hierarchy, but it sometimes requires renaming the functions to avoid conflicts.

My Pure-Fortran project has a script to warn about the unsafe use of optional arguments. For the code

module m
implicit none
contains
function max2(x1, x2) result(xmax)
real, intent(in) :: x1
real, intent(in), optional :: x2
real :: xmax
if (present(x2)) then
   xmax = max(x1, x2)
else
   xmax = x1
end if
end function max2

function max_(x1, x2, x3, x4, x5) result(xmax)
real, intent(in) :: x1, x2
real, optional, intent(in) :: x3, x4, x5
real :: xmax
xmax = max(x1, x2)
if (present(x3)) xmax = max(x3, xmax)
xmax = max2(xmax, x4) ! allowed since 2nd argument of max2 is OPTIONAL
xmax = max(xmax, x5)  ! unsafe since x5 may not be PRESENT
end function max_
end module m

use m
print*,max_(3.2, 2.6)
print*,max_(3.2, 2.6, 4.5)
end

python xoptional.py xoptional.f90 gives

1 optional-argument guard finding(s) in 1 file(s) (definite 1, possible 0).
xoptional.f90: 1

First finding: xoptional.f90:22 function max_ x5 [definite] - optional argument may be used without guaranteed PRESENT() guard

In the 2023 standard could this be handled like so?

a = (present(a) ? a : a_default)

I agree, but this leads to the question of whether

xmax = max( x1, x2, x5 )

is safe. Even though the arguments 3 and beyond are optional in the syntax of the max() intrinsic, they are not optional arguments in the formal sense, so I think the answer is that this is also unsafe. It seems that all of the arguments to the intrinsic max() function are required to be present.

Be wary though of possible performance penalties that can be unadvertely introduced due to the way some compilers interpret associate Fortran skill markdown for codex - #22 by ivanpribec it probably won’t be an issue for scalar optionals but it could if the optional is an array.

I think this is unsafe. The expression on the rhs is safe, but if a is not present, then this would be an assignment to that argument.

Then I suppose this would be better:

a = (present(a_in) ? a_in : a_default)

where a_in is the name of the optional argument and a is a local variable.

This is kind of a trivial issue, but here is the problem with this.

Fortran subroutines can be called with positional arguments, keyword arguments, or a combination of both. When you use keyword arguments, you want the dummy argument names to be as descriptive and mnemonic as possible. This makes adding suffixes such as ‘_in’ to otherwise meaningful dummy argument names unsatisfactory.

On the other hand, as was pointed out, if the good variable names are used for the dummy arguments, then the weird names with the ‘_local’ suffix need to be used internally.

The ASSOCIATE approach allows the local name and the dummy argument name to be the same, but it does have some restrictions (i.e. some attributes such as allocatable no longer apply).

1 Like

Hmm. Particularly with an intent(in) argument would this work? I am not using a compiiler that has the condition?true:false syntax. Normally I would avoid using the
same name for an associate-name and a variable in the parent scope (to the point I have never tried it) but in this case it might be appropriate is this would work?

program testit
   call one(987654321)
contains
   subroutine one(a)
      integer,intent(in) :: a
      associate (a => (present(a) ? a : 100) )
         write(*,*)a
      end associate
   end subroutine one
end program testit

This appears to work (just pretend using stdlib and so optval is already available):

program testit

   call one(87654321)
   call one()

contains

subroutine one(A)
integer,intent(in),optional   :: A
associate (A => optval_i(A,100) )
   write(*,*)A
end associate
end subroutine one

pure elemental function optval_i(x, default) result(y)
integer, intent(in), optional :: x
integer, intent(in)           :: default
integer                       :: y

   if (present(x)) then
      y = x
   else
      y = default
   endif

end function optval_i

end program testit

Output

    87654321
         100

VALUE to the rescue.

4 Likes

This F2023 feature was implemented in gfortran in the latter part of 2025. So if you build a current snapshot, it’s there. Works very nicely. Not sure what other compilers support it yet.

I think I’m not doing the right incantation.

program foo
   call bar(42)
   call bar()
contains
   subroutine bar(a)
      integer, value, optional :: a
      if ( .not. present(a) ) a = 100
      write(*,*) 'present(a)=', present(a), 'a=', a
   end subroutine bar
end program foo

This works with gfortran, but not with flang.

$ flang --version foo.f90 && a.out
Homebrew flang version 22.1.3
Target: arm64-apple-darwin25.4.0
Thread model: posix
InstalledDir: /opt/homebrew/Cellar/flang/22.1.3/libexec
Configuration file: /opt/homebrew/Cellar/flang/22.1.3/libexec/flang.cfg
Configuration file: /opt/homebrew/Cellar/flang/22.1.3/etc/clang/arm64-apple-darwin25.cfg
 present(a)= T a= 42
Segmentation fault: 11
1 Like

ifx 2025.3 and nvfortran 26.1 also segfault: Compiler Explorer

Goes to show that Fortran programmers don’t make use of the VALUE, OPTIONAL attribute pair very often…

NAG Fortran:

> nagfor test_optional.f90 && ./a.out
NAG Fortran Compiler Release 7.2(Shin-Urayasu) Build 7203
[NAG Fortran Compiler normal termination]
 present(a)= T a= 42
 present(a)= F a= 100

LFortran (version 0.50 and higher) works too! Proof: Compiler Explorer

Cray Fortran v20:

$ crayftn -O0 test_optional.f90 && ./a.out
 present(a)= T a= 42
Segmentation fault (core dumped)

With higher optimization levels it just skips the second bar call…

$ crayftn -O2 test_optional.f90 && ./a.out
 present(a)= T a= 42

I wonder what happens if bind(C) is added; should be illegal IMO. I just gave it a try:

Compiler results

Cray Fortran:

ftn-630 ftn: ERROR BAR, File = test_optional.f90, Line = 5, Column = 19
  Object "A", a dummy argument to a BIND(C) routine must not have both the value and optional attributes.

LFortran:

code generation error: asr_to_llvm: module failed verification. Error:
Store operand must be a pointer.
  store i32 100, i32 %a, align 4

gfortran:

Error: Variable 'a' at (1) cannot have both the OPTIONAL and the VALUE attribute because procedure 'bar' is BIND(C)

ifx:

/app/example.f90(5): error #8238: A dummy argument with the VALUE attribute cannot have the OPTIONAL attribute in a BIND(C) procedure.   [A]
   subroutine bar(a) bind(c)
------------------^

flang

error: Semantic errors in /app/example.f90
/app/example.f90:6:35: error: VALUE attribute may not apply to an OPTIONAL in a BIND(C) procedure
        integer, value, optional :: a
                                    ^

nvfortran

NVFORTRAN-S-1269-Dummy argument a cannot have both the VALUE and OPTIONAL attributes because procedure bar has the BIND attribute (/app/example.f90: 6)
NVFORTRAN/x86-64 Linux 26.1-0: compilation completed with severe errors

NAG Fortran:

NAG Fortran Compiler Release 7.2(Shin-Urayasu) Build 7203
Error: test_optional.f90, line 9: Argument A of BIND(C) procedure BAR has both the OPTIONAL and VALUE attributes

All compilers caught the problem, however the LFortran compiler message is not clear (reported here)

It would have never crossed my mind that this was meant to work. Looks interesting and promising. This is the first time I see these two put together.

This looks like a good solution to this optional argument issue. Basically the value attribute replaces the intent(in) attribute, but it allows the argument to be safely modified. If only compilers supported it in a reliable way.

The other combinations of intent() and optional still need some workaround effort on the programmer’s part.

1 Like