Skip to content

Deprecate and remove implicit save behavior #40

Open
@certik

Description

@certik

Currently, if you do:

integer :: x = 5

Fortran implicitly adds a save attribute:

integer, save :: x = 5

This is a common gotcha (https://www.fortran90.org/src/gotchas.html#variable-initialization-using-initialization-expression). I would like this behavior to get deprecated somehow, then removed, and instead use the syntax integer :: x = 5 to mean that the variable gets initialized to 5. Because this implicit save behavior is used extremely rarely in modern Fortran codes.

One approach would be that when you declare a module, you do something like:

implicit save(.false.)

Right next to implicit none (we should find a better syntax), and then this behavior is disabled. There are other approaches.

Activity

FortranFan

FortranFan commented on Oct 23, 2019

@FortranFan
Member

Bravo!

See a recent thread with lots of debate at comp.lang.fortran!

Note Fortran 2018 standard introduced

   implicit none (type, external)

WG5 document N2161 by J. Reid states, "The appearance of external requires that the names of external and dummy procedures with implicit interfaces in the scoping unit and any contained scoping units be explicitly declared to have the external attribute. The appearance of type requires the types of all data entities in the scoping unit and any contained scoping units to be explicitly declared"

The standard could extend the above abomination with

   implicit none (type, external, save)

Or why not short-circuit all this once and for all with a new

   EXPLICIT ALL

Or better yet, make one BIG BRAVE LEAP forward by changing the default in all scopes to the equivalent of implicit none (type, external, save) so most coders do not have to worry about IMPLICIT semantics any longer!

Off-topic: can anyone point to a link/reference to any current OPEN codebase in FORTRAN or modern Fortran that employs implicit typing and save (i.e., no 'implicit none' but the implied use of 'implicit integer(i-n), real(a-h,o-z)', etc.) per code design i.e., not as an outstanding bug, or a legacy carry-over that is simply awaiting code refactoring.

certik

certik commented on Oct 23, 2019

@certik
MemberAuthor

I think there should be a mechanism to fine grain these things (such as implicit none (type, external, save)). But then I would actually suggest that if you do not have any implicit none present, then by default it would be your explicit all. And have a compiler option that can do the old behavior, so that you can enable it for legacy code. This idea might be an uphill battle, and I might be convinced otherwise, as I do not want to break backwards compatibility. But let's at least consider it. It would be great if things "just worked" by default.

aradi

aradi commented on Oct 23, 2019

@aradi
Contributor

I absolutely agree! However, while deprecating integer :: x = 5 may be done at some point, I don't think it is reasonable to allow this syntax later again, but with a different meaning as it had before (simple initialization instead of implicit save).

@FortranFan EXPLICIT ALL is a great idea! It would not break backwards compatibility, and with one simple line you could enforce good programming practice. Whether it ever becomes default, I doubt.

certik

certik commented on Oct 23, 2019

@certik
MemberAuthor

@aradi are you against allowing integer :: x = 5 to mean initialization only if implicit none (save) is added? That would not break backwards compatibility.

aradi

aradi commented on Oct 23, 2019

@aradi
Contributor

If you introduce implicit none (save) or explicit all then that would be OK! But, then, it would be not a "deprecation" of implicit save (which maybe the committee won't support anyway), but just adding a new feature to change the behaviour of integer :: x = 5 if explicitly asked for. (And we could hope, that compiler vendors will offer appropriate switches to enforce explicit all during compilation, even if it had not been specified.)

certik

certik commented on Oct 23, 2019

@certik
MemberAuthor

@aradi I see. I am fine almost either way, as long as the natural syntax integer :: x = 5 can eventually mean initialization, one way or another.

gronki

gronki commented on Oct 28, 2019

@gronki

Well, I think the "no implicit save" behavior should be the default. It was introduced 30 years ago, which is not long for the industry standards (most codes until 2000s were still written in F77), therefore I don't believe changing the default behavior and then compiler vendors providing switches to satisfy their government customers would hurt anyone. Some people argue that "it's again Fortran spirit" but that the dark side of Fortran spirit I'd rather have nothing to do with. Dominik

jacobwilliams

jacobwilliams commented on Nov 12, 2019

@jacobwilliams

I've seen implicit save cause so many problems over the years. I've literally never seen it used on purpose, only by mistake. It's just a weird and terrible feature and needs to go.

gronki

gronki commented on Nov 13, 2019

@gronki

I will argue that the solution of using a switch to not break old code is inherently dangerous. Either the default behavior should be changed or a new syntax should be used. Here is my argumentation. (Note: this is a cross-post from another thread.)

First, let me prefice that with what I consider the absolutely basic design feature I expect Fortran to have: prevent silent errors. So ugly syntax or not, what I personally get paid for is to get good numeric results.

Consider the very root of such solutions, which probably is the familiar line

implicit none

The implicit typing feature was deemed to be unsafe and thus new, stricter rules for typing can be enabled with this switch. Please notice:

  1. if you omit the implicit none, the functionality of the code will not change
  2. if you use -fimplicit-none (or equivalent) switch, the functionality of the code will not change

Reading the code mid-file, you don't need to check the top of the file to see the implicit none. You just use the implicit typing and whether this mode is enabled or not, you will be good. Now if you start using implicit typing and implicit none was in effect, you will get a compiler error. All good.

Now consider the proposed switch (let's stick to my favorite implicit save). For example:

implicit nosave

If you do that, the behavior of the code will change violently in a completely non-distinguishable way. Whenever you see:

integer :: i = 0

you have no idea whether i will be zero on each call or not. And worst of all, if you make a mistake, you will not be warned.

Now when you work with different files, some of them written in 1980s, other in 2010s, you cannot just write right Fortran: you have to keep in mind which typing/saving/... rules are in place. Which is exactly the reason why implicit typing was deemed unsafe!

As much as I hate and despise this feature, I think we cannot provoke such situations. It must be clear looking at the code whether the variable is save or not. A couple of solutions have been proposed:

  1. integer, nosave :: i = 0 -- IMO pain to type
  2. integer, init :: i = 0 -- nicer to type on QWERTY keyboard
  3. integer :: i := 0 -- or some other operator, such as =>. The good side is that there are no keywords that clutter the code.

Any solution must be nice to type. This keyword/syntax will be used a lot. So I personally would prefer some operator-based syntax because having nosave every 3 lines would cause a lot of clutter, especially in editors with syntax highlighting. But the worst case init would be not that much pain.

certik

certik commented on Nov 13, 2019

@certik
MemberAuthor

@gronki I agree with your overall sentiment here and many of your points. Here is the best proposal that I have so far. All of these will be allowed:

  1. integer, init :: i = 0
  2. integer, save :: i = 0
  3. integer :: i = 0

The 1. will initialize the variable, the 2. will do the save attribute, and finally 3. will emit a compiler warning that you are using "implicit save" feature which is deprecated, and you should convert 3. to 2.

aradi

aradi commented on Nov 13, 2019

@aradi
Contributor

That's a reasonable proposal. However, one should keep in mind, that if combined with the possibility of variable declarations at arbitrary positions (#81), one would probably see a lot of

! some code
integer, init :: i = 0
! some more code

kind of lines, which when occuring too often I find still more verbose then it should be. (But on the other hand, I agree, it is explicit and does not break backwards compatibility.)

An other question: What would those assigments do within a block statement?

subroutine test()
  ! some code
  block
    integer, init :: i = 0
    integer, save :: i = 0
    integer :: i = 0
  end block
end subroutine test

Would any of these be allowed, and if yes, with which behavior?

66 remaining items

jme52

jme52 commented on Jan 17, 2020

@jme52

@klausler

See 8.5.16, paragraph 4. Variables declared in modules, submodules, and main programs implicitly have the SAVE attribute, initialized or not.

Sorry, you are right - I don't know what I was thinking. Let me rephrase: At the moment,

  • "Explicit initialization of a variable that is not in a common block implies the SAVE attribute, which may be confirmed by explicit specification." (section 8.4, paragraph 3).
  • "A variable, common block, or procedure pointer declared in the scoping unit of a main program, module, or submodule implicitly has the SAVE attribute, which may be confirmed by explicit specification." (section 8.5.16, paragraph 4).

Both are cases where the SAVE attribute is implicit. If an implicit none(save) statement option is introduced, I think it will be confusing if it only affects variables that obtain their implicit SAVE via explicit initialisation.

jme52

jme52 commented on Jan 17, 2020

@jme52

I think we agree on implicit none(save), init and save within a subprogram - please do check:

subroutine foo()
   implicit none(save)
   integer :: n  ! No initialisation, so everything ok.
   real, save :: a = 1. ! Requires explicit save. Would have been
                        ! implicitly saved if implicit none(save) was missing.
   logical :: c = .False.
   init :: c, q  ! we need to provide the list because,
                 ! if not, the uninitialised n would be an error
                 ! Declarations may appear above or below the statement
   integer :: q = 3
   [...]
function bar()
   init
   integer :: n = 2     ! If it wasn't initialised it would be an error.
   real, save :: a      ! Overrides the function-wide init.
                        ! save does not require initialisation.
   [...]
subroutine baz()
   integer :: n = 2     ! Initialisation implies implicit save.
   real, init :: a = 1. ! Overrides the default implicit save.
   [...]

(modulus the existance of the bare init statement, but I think the real problems are others, see my next post).

jme52

jme52 commented on Jan 17, 2020

@jme52

What we may not agree on yet is:

  1. Effect of implicit none(save) on variables (initialised or not) declared in a main program, module or submodule. Do we want them to have the same rules subprograms will have, or different ones?
  2. Inheritance of implicit none(save): currently other implicit nones affect contained subprograms or BLOCK constructs. Do we want the same for this third implicit none spec or not?
  3. Inheritance of save and init statements: currently a save statement only affects its scoping unit (see definition in 3.123: "BLOCK construct, derived-type definition, interface body, program unit, or subprogram, excluding all nested scoping units in it"), i.e., is not inherited. Do we want to keep this, and extend this rule to the init statement?
jme52

jme52 commented on Jan 17, 2020

@jme52

My current views on them:
-> 2) Yes, same inheritance rules for all spec's of implicit none, for simplicity of the rules and of the code.
-> 3) Yes, save and init statements should only affect the current scoping unit, there is no need to inherit them - they should be tuned in every contained subprogram or block.
-> 1) Yes, I want the same rules for program units and subprograms. When using implicit none(save) in the program unit, we can recover the old program unit-wide implied save by adding a spec-less save there. Since save is not inherited by contained subprograms, this won't affect them.

Example:

module mym
   implicit none(save)
   save ! only affects module variables
   integer :: n ! saved even if not initialised
   real, init :: x = 0. ! unlikely to be of much use, but possible
contains
   subroutine sub()
      integer :: l, m   ! not saved because they are out of the scope of the module save
      integer, init :: p = 3
      real, save :: x = 2. ! requires explicit save because of initialisation +
                           ! inherited implicit none(save)
[...]
aradi

aradi commented on Jan 17, 2020

@aradi
Contributor

@jme52 I agree on your view for 2).

As for 1): What meaning do you suggest then for a module variable in presence of implicit none(save) but in absence of an init attribute:

module testmod
  implicit none(save)
  integer :: n
end module testmod

Would this behave like a global (saved) variable (as it is now)? Or would it when a module goes out of scope (whatever that means) loose its value? I think, from your argumentation latter would follow, but I definitely would prefer the former.

As on 3): I would argue against having an init keyword at all, I'd propose to have it only in the attribute form. If we allow for its keyword version, we again come back to the original problem: A line somewhere in the code changes the meaning of other lines somewhere else, as in:

module testmod
! Note: no `implicit none(save)`  had been specified
contains
[...]
  subroutine testsub()
    integer :: a = 1   ! Is this saved or not? Not known!
    [...]
    init :: a                ! Now, it turns out, a will not be saved.

I think, such a scenario would degrade code readability a lot! Furthermore it would be basically equivalent the original proposal to let implicit none(save) turn all assignments at a declaration into a non-saved assignment, but doing it more complicated (with two keywords instead of one). There was no consensus on that (although I still favor it...), this is why the init attribute was suggested as a compromise.

certik

certik commented on Jan 17, 2020

@certik
MemberAuthor

What does save mean for module level (or program level) global variables? The Fortran module never goes "out of scope". If we equate save for Fortran with static for C, then C allows to have global variables that are non-static. But in Fortran's semantic, that would make no sense. The standard talks about save in section 8.5.16, and the definition of save is that

The SAVE attribute specifies that a local variable of a program unit or subprogram retains its association status, allocation status, definition status, and value after execution of a RETURN or END statement unless it is a pointer and its target becomes undefined (19.5.2.5(6)). If it is a local variable of a subprogram it is shared by all instances (15.6.2.4) of the subprogram.

If this is the definition, then it makes no sense for module variables or the main program because there is no end or return.

What exactly does this mean:

A variable, common block, or procedure pointer declared in the scoping unit of a main program, module, or submodule implicitly has the SAVE attribute,

because they didn't define what save means in this case...

Unless somebody can clarify that, I will continue assuming that module level and program variables do not have save attribute, because it makes no sense. So implicit none(save) (obviously) does not apply to them either.

certik

certik commented on Jan 17, 2020

@certik
MemberAuthor

What does it mean for a variable in a module to have a save attribute? I know you can write code like this:

module a
implicit none
integer, save :: x
integer :: y
end module

where x has explicit save and y has implied save attributes. But I do not understand what that (implied) save means in this case. I quoted the definition above, and that definition does not seem to apply for this case.

certik

certik commented on Jan 17, 2020

@certik
MemberAuthor

@klausler thanks a lot for the explanation. I didn't realize that the module variable could have been destroyed between subroutine calls if it didn't have the implied save attribute. You are right that it make sense that common blocks could override each other to save memory in the early days of computing. I think EQUIVALENCE was used for the same reason.

milancurcic

milancurcic commented on Jan 17, 2020

@milancurcic
Member

I didn't realize that the module variable could have been destroyed between subroutine calls if it didn't have the implied save attribute.

This is not true. All variables declared in a module (initialized or not) have the save attribute automatically and implicitly and I think that's what is meant by this:

A variable, common block, or procedure pointer declared in the scoping unit of a main program, module, or submodule implicitly has the SAVE attribute

For example, if you declare this in a module:

module mymod
  ...
  integer :: a ! value of a is preserved between procedure calls and module uses
end module mymod

and use it from other modules, procedures, or a main program, it's value is preserved between uses. It's a true global.

But this is also why the implicit save rule doesn't matter for modules and programs:

module mymod
   ...
   integer :: a ! value of a is preserved
   integer :: b = 1 ! value of b is initialized and also preserved
end module mymod

This is why the caveat of implicit save is irrelevant for modules and programs.

certik

certik commented on Jan 17, 2020

@certik
MemberAuthor

@milancurcic we are in agreement (you might have misunderstood my comment). @klausler was explaining the history behind this, that in a common block, if you didn't have the save attribute, the contents could get destroyed. And a module is successor of a common block, and from the beginning they made all variables implicitly saved, so that their contents cannot get destroyed. So the reason they have an implicit save is so that they do not behave like variables in a common block. That's what I meant by:

I didn't realize that the module variable could have been destroyed between subroutine calls if it didn't have the implied save attribute.

milancurcic

milancurcic commented on Jan 17, 2020

@milancurcic
Member

Ah, okay, I missed that piece, great!

added
Clause 8Standard Clause 8: Attribute declarations and specifications
on Apr 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Clause 8Standard Clause 8: Attribute declarations and specifications

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @certik@everythingfunctional@aradi@milancurcic@jacobwilliams

        Issue actions

          Deprecate and remove implicit save behavior · Issue #40 · j3-fortran/fortran_proposals