Skip to content
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

Deprecate and remove implicit save behavior #40

Open
certik opened this issue Oct 23, 2019 · 83 comments
Open

Deprecate and remove implicit save behavior #40

certik opened this issue Oct 23, 2019 · 83 comments
Labels
Clause 8 Standard Clause 8: Attribute declarations and specifications

Comments

@certik
Copy link
Member

certik commented Oct 23, 2019

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.

@FortranFan
Copy link
Member

FortranFan commented Oct 23, 2019

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
Copy link
Member Author

certik commented Oct 23, 2019

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
Copy link
Contributor

aradi commented Oct 23, 2019

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
Copy link
Member Author

certik commented Oct 23, 2019

@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
Copy link
Contributor

aradi commented Oct 23, 2019

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
Copy link
Member Author

certik commented Oct 23, 2019

@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
Copy link

gronki commented Oct 28, 2019

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
Copy link

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
Copy link

gronki commented Nov 13, 2019

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
Copy link
Member Author

certik commented Nov 13, 2019

@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
Copy link
Contributor

aradi commented Nov 13, 2019

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?

@certik
Copy link
Member Author

certik commented Nov 13, 2019

To be honest I've never used the block statement, so I don't know yet: is there any reason why any of that would not work, once #81 is implemented?

A bigger issue that I can see is this: many times codes declare more than one variable on one line, such as:

integer :: i, j, k, l, a

And if I want to initialize a, but not the rest, I have to now split it:

integer :: i, j, k, l
integer, init :: a = 5

But perhaps that's fine, similarly to specifying dimensions like:

integer :: i, j, k, l, a(:)

versus:

integer :: i, j, k, l
integer, dimension(:) :: a

Update: although at that point, it's shorter to use the current syntax:

integer :: i, j, k, l, a
a = 5

instead of:

integer :: i, j, k, l
integer, init :: a = 5

Especially after #81 is implemented.

@milancurcic
Copy link
Member

All of these will be allowed:

   integer, init :: i = 0
   integer, save :: i = 0
   integer :: i = 0

I agree this is the way forward. I plan to write proposals (in this repo) for #22 and #81 to be considered for f202y during the Feb 2020 meeting -- I hope @certik will agree to contribute and advocate for them. I think it'd be important that this proposal (deprecate implicit save) gets going at the same time, as #81 and this proposal support each other design-wise. The init attribute syntax proposed here could also be used in support of #22 if applied to an optional dummy argument.

@septcolor
Copy link

septcolor commented Nov 14, 2019

Golang has var := expr; which declares var to have the type of the value of expr, which is also assigned to it. The fact that an initializing declaration must necessary have an expr does make the explicit type somewhat redundant on the statement. So I suggest AUTO :: a = expr, b = expr, ... and AUTO, POINTER :: p => target, ....

While I feel the init keyword appealing (also to distinguish from save), the above auto approach also seems nice in that it allows type inference and avoids the need to write a (possibly long) type name (which is redundant if a structure constructor is used on the RHS, in a way similar to the Java case below). As for keyword,var :: a = expr might also be nice (because one character shorter).

https://dzone.com/articles/finally-java-10-has-var-to-declare-local-variables

@septcolor
Copy link

septcolor commented Nov 14, 2019

A bigger issue that I can see is this: many times codes declare more than one variable on one line, such as:

integer :: i, j, k, l, a

And if I want to initialize a, but not the rest, I have to now split it:

integer :: i, j, k, l
integer, init :: a = 5

I think we can just write

integer, init :: i, j, k, l, a = 5

but is this problematic? (Here, i,j,k,l are given no initial values.)

Another approach might be to define the default initial value (e.g., 0 for integer) if not specified in a line with init. But I am not sure if it works for derived types.

@gronki
Copy link

gronki commented Nov 14, 2019

I don't know what implications for compiler optimizations and performance would the default value requirement have.

@aradi
Copy link
Contributor

aradi commented Nov 14, 2019

Another approach might be to define the default initial value (e.g., 0 for integer) if not specified in a line with init. But I am not sure if it works for derived types.

I think, default initialization if a value is not provided is not a good idea. In my experience it often makes finding obvious bugs (e.g. a variable has not set an explicit value before it is used) harder, as one always get consistent results (just the wrong ones).

Also, if the init attribute will be the favorite (I can not come up with anything better either), I would not allow for

integer, init :: a, b, c = 2

This line suggest the picture of creating initialized integers a, b and c (since attributes apply to all entities after ::), but only initializes c in reality. So, I think, it is didactically sub-optimal.

On the other hand, if it were the nosave attribute (which I like much less), then

integer, nosave :: a, b, c = 2

would be meaningful, as it tells you that neither a, nor b or c will be saved, which is indeed what happens. (And = 2 tells you, that c will always have the value 2).

Further question: Would pointer assignment be allowed for a well?

@gronki
Copy link

gronki commented Nov 14, 2019

Or, if we use a new operator, the statement gets self-consistent (but maybe less visible):

! a is uninitialized, b is static, c is initialized
integer :: a, b = 3, c => 4

@aradi
Copy link
Contributor

aradi commented Nov 14, 2019

Yes, but if we introduce a new operator for assignment on declaration, and later maybe also allow initialization of pointers on declaration, then one would have to invent yet an other operator. So, I'd then rather favor an attribute.

My only concern is exploding verbosity. Especially when #81 is also considered, you will have all around the code this additional init or nosave popping up. I still sympathize with the idea of @FortranFan to have explicit all instead of implicit none (none, external, save) at the beginning of the module scope, and then just write

integer :: i = 2

and be surprised, that it exactly does what it should. 😄

@gronki
Copy link

gronki commented Nov 14, 2019 via email

@certik
Copy link
Member Author

certik commented Nov 14, 2019

Thank you everybody, we are moving nicely with the various pros and cons. I like that you could write

integer, init :: a, b, c = 2

to mean that a and b are not initialized, but I agree with @aradi that it's not consistent with the fact that init is applied to a and b. And I also agree that things should not initialize automatically to 0.

I also agree that this will mean that init will start popping all over the place. And by looking at how people use this feature, you can apparently use integer :: a, b, c = 2 in the main program, in module variable and in derived types already, and it will not imply save.

Given all this, I am now leaning towards simply making integer :: a, b, c = 2 to imply init.
In order to get this in, I think we have to have two proposals. One is to introduce init, and I think that integer, init :: a, b, c = 2 should mean that a is not initialized, even if it's slightly inconsistent. This proposal does not break backwards compatibility.

The reason is that we will have a second proposal that changes the syntax integer :: c = 2 to mean implied init instead of implied save. After this second proposal is in, you can simply go into your code and remove init and things will still work. This proposal would break backwards compatibility, and so again it probably must be done in two stages. First it will introduce implicit none(save), which will not break compatibility. Then a second proposal will make implicit none(save) the default. The very last proposal might not pass --- but that will come in the category of simply making implicit none(all) the default, which is a separate discussion (perhaps it will just be a compiler option that would be recommended to always use).

@certik
Copy link
Member Author

certik commented Nov 14, 2019

If we have IMPLICIT NONE(SAVE), however it's spelled, there's no need for an INIT or NOSAVE attribute on a type-declaration-stmt, right?

That's a good point. The only argument for keeping init anyway that I can think of is that it would allow you to use it even when the implied save is enabled.

My initial idea was to introduce this feature slowly, without needing implicit none(save) initially. But you are right, since the goal is to have implicit none(save) eventually, we might as well just do it directly, and not even introduce init.

@certik
Copy link
Member Author

certik commented Nov 14, 2019

Let's also learn from how C++ handles a similar situation. They would like to use the syntax a[1, 2, 3] for multidimensional indexing. Currently they can't do that because the comma operator already has a special (incompatible) meaning. So they first deprecated it in C++20: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1161r2.html, and then later, after a reasonable time, they will change its meaning, as described in the proposal. The comma operator is very rarely used, it was simply a mistake, similarly to implied save, to assign a very rare feature to a commonly used syntax.

@aradi
Copy link
Contributor

aradi commented Nov 15, 2019

@certik I think, we should be pragmatic. If the feature is introduced in two steps, people should need as few code line changes as possible to keep up with it. That would indeed suggest to drop the idea of init and keep with the following two steps:

  1. Introducing a keyword making non-save behavior the default within a given scope, e.g. implicit none(save), and I would probably also suggest a proposal for allowing explicit all as a synonym for implicit none(type, save, external).

  2. At a later stage require implicit none(save) (or even explicit all) as default behavior when nothing has been specified explicitly.

People would only have to change one single line in every module to adapt. And in case the 2nd step never gets accepted (as it breaks backwards compatibility), it is still easier to convince people about using good programming practices, if they have to change / add a single line to their code as opposed to add the init attribute all over.

@tskeith
Copy link

tskeith commented Nov 19, 2019

Are there any compilers that warn about implicit save? It seems like that would be a good place to start to help beginners and detect code that depends on that behavior.

@certik
Copy link
Member Author

certik commented Jan 16, 2020

@klausler looks like the answer to your question whether we can agree on this is that yes, I think we can agree this is a reasonable starting point.

If that's the case, we can now move on to the second part of this, which is, whether to use init or auto (and auto as in #129 or some other way) and all the details of that.

@septcolor
Copy link

septcolor commented Jan 16, 2020

Just one additional question/example. Let's assume, implicit none(save) disables assignment on declaration, unless you specify either init or save, would then the following snippet be also invalid?

module testmod
  implicit none(save)

  integer :: i = 2

end module testmod

Because module variables are global variables, isn't it OK to assume that they are always saved? I guess it is also a common behavior in C-like languages (so I feel it more natural for implicit none(save) to have no effect on the current behavior of module variables).

#include <stdio.h>

int xg = 1;  // global

void test() {
    static int xs = 1;  // static or saved
    int xl = 1;  // local
    xg += 1;
    xs += 1;
    xl += 1;
    printf("xg=%d xs=%d xl=%d\n", xg, xs, xl);
}

int main() {
    test();  test();  test();
}
/* Result:
xg=2 xs=2 xl=2
xg=3 xs=3 xl=2
xg=4 xs=4 xl=2
*/

@aradi
Copy link
Contributor

aradi commented Jan 16, 2020

Allowing the automatic type setting in connection with init as in

init :: n2 = 2

would make init and save asymmetric, since you can not make

save :: n2 = 2

either.

@certik
Copy link
Member Author

certik commented Jan 16, 2020

@klausler thanks for the proposal. Now we are moving forward. How about this case:

integer, init :: n1 = 1, n2

Would this be a compiler error and if not, would n2 be not initialized?

@klausler
Copy link

@klausler thanks for the proposal. Now we are moving forward. How about this case:

integer, init :: n1 = 1, n2

Would this be a compiler error and if not, would n2 be not initialized?

It would be a user program error if n2 were not initialized somewhere in the specification-part. It would not necessarily have to be done there.

@certik
Copy link
Member Author

certik commented Jan 16, 2020

How about this case:

integer :: n1, n2
init :: n1 = 1

Would that be allowed and n2 will not be initialized?

@certik
Copy link
Member Author

certik commented Jan 16, 2020

Ok. So I think the proposal should also mention the motivation why we need the bare init and save statements. The motivation that I can see is that when I have some existing code with declarations:

integer :: a, b, c, d, e, f
real(dp) :: x, y, z
real(dp), allocatable :: h(:,:), t(:)
...

and decide that variable c should be initialized to 0, then I can do it just by adding init :: c = 0 right underneath the declaration of c:

integer :: a, b, c, d, e, f
init :: c = 0
real(dp) :: x, y, z
real(dp), allocatable :: h(:,:), t(:)
...

and do not need to modify the previous declaration by removing c from it. Then later, once I am done developing my subroutine, I can refactor the above code to (perhaps some auto formatting can do that):

integer :: a, b, d, e, f
integer, init :: c = 0
real(dp) :: x, y, z
real(dp), allocatable :: h(:,:), t(:)
...

A counter argument against adding init :: c = 0 is that one can do the same just by adding c = 0 code there as follows (which is shorter and can be done already):

integer :: a, b, c, d, e, f
real(dp) :: x, y, z
real(dp), allocatable :: h(:,:), t(:)
...
c = 0

But the downside is that the initialization is far from the declaration, while the init :: c = 0 allows the initialization to be right next to the declaration, which makes it much easier to later refactor into integer, init :: c = 0, as shown above.

@jme52
Copy link

jme52 commented Jan 16, 2020

@klausler, @certik: I think that what you are proposing about automatic typing and dimensioning is really something different to what this thread was about. To me you are starting to get too close to just being able to declare n=3 without init, save or parameter. Do you think this could be discussed separately? It does not really need the other changes discussed in this thread - it could apply to existing save statements, e.g..

@jme52
Copy link

jme52 commented Jan 16, 2020

I agree with the possibility of init statements and attributes analogous to the existing save statements and attributes - I already tried to outline how they could work in #40 (comment) .

About whether init requires variables to be initialised, I am not sure what extent that can have, and if that's a good idea:

  1. saved variables don't need to be given a value, so that would be an asymmetry vs. init.
  2. Assume the following snippet:
    subroutine calculate_and_print()
    init   ! I don't want to be caught with that save stuff,
           ! so I make sure nothing is saved unless explicitly requested.
    logical :: solution_found = .false.
    integer, save :: number_of_executions = 0
    integer :: n=0
    real :: a, b, c(:)
    That looks like reasonable programming to me. Would that be an error following your logic because a, b, c(:) are not initialised?
    If it is an error, it tremendously limits the power of an init statement that is not followed by an explicit list of names.
    If it is not an error, because the standard says that an init statement without a list works different to an init statement with a list and/or to an init attribute in a declaration... well, it can make sense (integer, init :: n does look like a bug), but it may be starting to get too complex...

@certik
Copy link
Member Author

certik commented Jan 16, 2020

I am not sure I like the bare init statement either. The proposal does seem to mix #129 with #40, I agree with @jme52 on this.

A more minimal proposal for init that hopefully we can all agree upon as a starting point is to allow:

integer, init :: x = 5

but not

integer :: x
init :: x = 5

nor:

init
integer :: x = 5

Btw, the last example's syntax is pretty much the same as:

implicit none(save)
integer :: x = 5

and I thought the main argument why we are even doing init is that we do not want to change the meaning of integer :: x = 5. So if we are going to allow bare init, then I vote to simply rename bare init to implicit none(save) and we arrive back at my original proposal.

@jme52
Copy link

jme52 commented Jan 16, 2020

@klausler said:

Variables in modules and submodules have the SAVE attribute implicitly already. IMPLICIT NONE(SAVE) should not apply to them. (Maybe this should also be the case for main programs; I could make a case either way.)

Variables in modules and submodules have the save attribute implicitly in the same way that other program units and subprograms, so that cannot be a reason to treat them differently.

I think it would be more fair to say that it makes more sense (to us?) to have saved variables in modules by default than to have them saved in functions and subroutines, where we are more likely to want them inited.

If we want implicit none(save) to have similar inheritance rules as implicit none or implicit none(external) (which are actually defined in different parts of section 8.7: paragraph 3 and constraint C895, respectively), then an implicit none(save) at the beginning of a module would also affect contained subprograms. However, having a module-wide implicit none(save) that does not have any effect in the module variables sounds very weird.

It would make more sense to enforce implicit none(save) everywhere, and keep the current limits of save (and init) to scoping units, so

module m
  implicit none(save)
  save ! needed to avoid error in m
  m = 0
contains
  subroutine s
    init ! needed because implicit none(save) is inherited from the host
    integer :: n = 0
    [...]
  end subroutine
end module

@certik
Copy link
Member Author

certik commented Jan 16, 2020

As far as I can see, the only difference between "bare init" and implicit none(save) (if both are used in a subroutine) is that bare init requires all variables to be initialized, while implicit none(save) makes this optional. Is there any other difference? Both features have the issue that the syntax integer :: x = 5 which previously implied save would now imply init. Also both are opt-in.

@jme52
Copy link

jme52 commented Jan 16, 2020

init   ! I don't want to be caught with that save stuff,
       ! so I make sure nothing is saved unless explicitly requested.

Are you confusing a bare INIT with an IMPLICIT NONE(SAVE)? Your comment seems to mean that you want IMPLICIT NONE(SAVE), not a bare INIT.

No, I am not confusing it. implicit none(save) forces everything to explicitly be either inited or saved, but does not prevent save by itself. init is the command that prevents save.

@certik
Copy link
Member Author

certik commented Jan 16, 2020

Aren't the three of us saying the same thing? It seems to me we are.

@aradi
Copy link
Contributor

aradi commented Jan 17, 2020

I think, they are two distinct issues here:

  1. Should implicit none(save) effect only routine variables or also module and program variables.

  2. Should the init statement be allowed.

As for 1: I am for letting implicit none(save) only act on routine variables. Having to specify save for every module variable, just because you specified implicit none(save) globally would be a pain. (And I would hate to specify implicit none(save) in each routine just to avoid its effect on module variables as well.) Additionally, it is not clear to me, what a non-initialized module variable would mean, if implicit none(save) is in effect, as in

module testmod
  implicit none(save)

  integer :: somevar

end module testmod

Would this variable get lost if the module "goes out of scope"?

As for 2: I agree with @certik , allowing the init statement would mean, you can write

module testmod
  init

contains

  subroutine test()
     integer :: n = 3
  end subroutine test

end module testmod

Then, we are back to the original proposal, that adding some module level keyword changes code behavior further down. I'd go for using init only as an attribute in the first round.

@jme52
Copy link

jme52 commented Jan 17, 2020

@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
Copy link

jme52 commented Jan 17, 2020

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
Copy link

jme52 commented Jan 17, 2020

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
Copy link

jme52 commented Jan 17, 2020

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
Copy link
Contributor

aradi commented Jan 17, 2020

@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
Copy link
Member Author

certik commented Jan 17, 2020

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
Copy link
Member Author

certik commented Jan 17, 2020

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
Copy link
Member Author

certik commented Jan 17, 2020

@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
Copy link
Member

milancurcic commented Jan 17, 2020

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
Copy link
Member Author

certik commented Jan 17, 2020

@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
Copy link
Member

Ah, okay, I missed that piece, great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Clause 8 Standard Clause 8: Attribute declarations and specifications
Projects
None yet
Development

No branches or pull requests