Why I can access private module variable in this program?

Hello all,

This is my first post here.

I just need a help to explain why I can access (both read and write) private module variable x in this program? is it a bug or I understood the private clause incorrectly?

imronuke@imronuke-p02:$ cat test.f90 
module foo1

    private

    real :: x = 1
    
end module

module foo2

    use foo1

    real :: y = 2

end module

program test

    use foo2

    x = 5
    write(*,*) x, y

end program


after compiling and running


imronuke@imronuke-p02:$ gfortran test.f90 -o test
imronuke@imronuke-p02:$ ./test
   5.00000000       2.00000000    

I am using gfortran 11.3.0

imronuke@imronuke-p02:~$ gfortran --version
GNU Fortran (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Thanks all.

2 Likes

You need to add ‘implicit none’ in each module and in your main program. Without you, are implicitly declaring a variable called ‘x’ in your main program, which is difference from the one in the module.

2 Likes

You need to add ‘implicit none’ in each module and in your main program. Without you, are implicitly declaring a variable called ‘x’ in your main program, which is difference from the one in the module.

OMG, how come I forgot this basic thing. Thank you.

Implicit typing strikes again.

1 Like

Continuing the discussion from Why I can access private module variable in this program?:

Implicit typing strikes again.

Yes, yes. IMHO, this is should have been enabled by default in modern Fortran compilers unless stated otherwise. I know there has been a long debate on this in the Fotran Community.

1 Like

@imronuke ,

Welcome to the discourse!

Fortran compilers, barring LFortran, do not appear at all keen to enable explicit typing by default. For them, there are always these unnamed but “bigger” and “more important” customers who won’t like that. That’s a real, real shame, both on these “customers” - who I reckon are entirely on taxpayer dole and thus can resist low-level changes like doing away with the implicit mapping in Fortran until hell freezes over - and on the compilers. But that’s how it will be.

So look into -fimplicit-none option if you are using gfortran.

One “bigger” and “more important” customer was the US Department of Defense. They introduced
implicit none in MIL-STD-1753 in 1978 as an extension to f77. That forced vendors to implement it (and various other features that seem much less contentious) if they wanted DoD to buy their compilers.

This is to give a balanced perspective of this approach. If you use the compiler option to “fix” the code, then you must use that option every time you compile your code from now to infinity. And everyone else who uses that code must do the same thing, from now to infinity. But if you put the declaration in the source once, just one single time, then it is fixed forever, and neither you nor anyone who uses the code will ever need to worry about it again, from now to infinity.

Readers can appreciate OP understands the need to include implicit none in each relevant scope but inadvertently missed out on doing so. OP shows 3 such scopes in the original post itself, but may have many more in actual code. Under the circumstances, the best guard rail is to also use the -fimplicit-none compiler option. OP can then proceed to use the compiler error messages to guide with the introduction of implicit none in the scopes as well as explicitly declare the types of the objects in question.

I agree with this. If compiler options help the programmer fix the code, then I think that is good. If they are used to compile nonconforming code indefinitely, as is often the case, then that is not good. In this case, it looks like the former situation.

Wouldn’t it be useful if a programmer could, at any point in the program, print out the names and the sources of all variables that are in scope? If that were done here, then the programmer could have easily seen that y is imported from the module foo2 and that x is a local variable, which is probably not what was expected.

As a matter of style, some programmers always use the only: clause with use statements. If that had been done here, the program might have looked something like

program test
    use foo2, only: x, y
    x = 5
    write(*,*) x, y
end program

and the compiler would know at compile time that x cannot be imported from the module foo2 (presumably as intended). Of course, implicit none would be a good idea too. In this case, both conventions could be used together.

Thank you for your comment. I used to be like that in writing Fortran programs. Later, I personally found that is not practical on a large project. For example I need to write use foo, only: x,y,z,bla,bla,.. which often there are teens of module variables/methods in that statement which I think is verbose. And if I modified my program, I need to take care that statement again by adding or removing module variables/methods. I prefer to use private or public clauses to control which variables/methods that can be used outside the module. Well, again, it is just matter of style.

Thank you for the comment. I came up with an idea that perhaps solve everyone problems, including those big customers: the implicit none shall be enabled by default on all Fortran files except the ones with extensions *.f and *.f77. However, if necessary, this behavior can be altered using a compiler flag.

2 Likes

Yes, public/private declarations are useful too. But the only: clause serves a different purpose. If you want to know which module an entity comes from, then if the only: clause is used, you just look at the local use statements and there is the answer. Without that, the programmer would need to search the source code for each individual module, and also each module that they use in turn, to locate the source.

Of course, only: does other things too. If there are multiple modules with entities with the same name, then only: provides a way to pick the one you want. And back to the other point, a programmer looking at the local code can see from which of the multiple possible modules the entity is imported.

The downside for this is that it places more burden on the programmer as he is writing and maintaining the code. Some compilers can warn the programmer at compile time if an entity is mentioned in an only: clause but is not used in the code. That helps the programmer keep the lists clean. The other problem is when there are dozens of entities that are included in each only: clause, all of which are actually referenced. It is perhaps easy to search these lists with a text editor to locate the important one, but it is more difficult for the programmer to just look at the code and find it. Ironically, it is larger programs, perhaps with shared modules spread over several directories, where the only: clause lists become most useful, and it is those cases also that tend to have the larger lists. An upside is that all of this checking is done by the compiler at compile time, so it is relatively fast and easy to do this as a code is being developed or refactored. If the check required runtime tests, perhaps with input data that specifically tested the relevant parts of the code, it would be overall a more difficult burden on the programmer than it is.

1 Like

The ability to specify which entities are imported is one of the things I really like about Fortran in comparison to C/C++, where it can take a lot of detective work to figure out the origin of some entity. In some cases your code may work because of a transitive include, but when you refactor it and remove a header which is not needed anymore, your code breaks because of a missing include.

C++ modules were introduced to resolve such problems; my own experience so far has been that learning the the distinct rules for modules/namespaces/headers is a burden. For example here is a three-part series on C++ modules which is hard to cover in one sitting.

1 Like

I can do this in VS Code equipped with Modern Fortran extension with a single button. Even, I can find the implementations of an entity in the other part of the project by a combination of buttons. This makes my life much easier (thanks to the developers Hansen and @gnikit)

Yes completely agree. I use only in case there is naming conflict or when the entities that are used are few. Thanks Ron for the discussion :slight_smile:

Unintentionally, it has been left that passing variables from modules is something acceptable/tolerant which could be a misleading idea to new users.

IMHO, passing variables from modules should be avoided. Modules are used to containerized code. Except of functions/subroutines, types, (and other structural declarations/definitions) a module may also contain PUBLIC PARAMETERs which are fixed variables (eg. PI) to avoid re-declaration of them within the scope of a code block (eg library).

1 Like

Indeed, and it is in LFortran:

$ lfortran a.f90
semantic error: Variable 'x' is not declared
  --> a.f90:21:5
   |
21 |     x = 5
   |     ^ 'x' is undeclared
$ lfortran --implicit-typing a.f90
5.00000000e+00 2.00000000e+00

Other compilers could do the same I think.

Your original code currently compiles with LFortran but not because of implicit typing, but because we have a bug with private variables (Disallow importing private variables · Issue #1239 · lfortran/lfortran · GitHub), which we’ll fix soon.

3 Likes

The problem with such blanket declarations is that there are so many exceptions, they are often misleading. There are only so many ways that a subroutine can access some datum. It can be local, or a dummy argument, imported from a parent subprogram, through a pointer, a common block, or a module. Many times, of all the possibilities, importing from a shared module is the best approach.

Didn’t you just show above this statement in your comment that it was indeed the --implicit-typing option that allowed it to compile and execute?

I also wanted to point out another feature of implicit none, which also applies to the equivalent compiler options. With either the standand default implicit mapping or with any other explicitly declared mapping, the implicit rules in place can affect how the code is compiled. If the implicit mapping in effect as the time applies to any undeclared entity, then it obviously affects the properties of that entity, specifically, its type and kind. If the implicit mapping is changed, it changes that entity.

But implicit none is different in this respect. With implicit none in effect throughout the code, there are no undeclared entities. The type and kind properties of each entity are determined with some kind of explicit declaration. This means that any program that compiles with implicit none in effect would compile exactly the same way with some other implicit declaration, including the fortran standard default implicit mapping. So when it comes to compiling code, implicit none does absolutely nothing in the end. Its real purpose is during the program writing stage, to help the programmer recognize any missing declarations. In this respect, implicit none is an odd language feature.

Going back to the original code in this discussion, the mistake was that the programmer thought (incorrectly) that x was imported from a module, when in fact it was a local variable that was implicitly typed (real with the default kind). As discussed above, there are two things that could be done so that the compiler could catch this error. One is to invoke implicit none (preferably in the source code, but also with a compiler option). The programmer could then decide exactly what he wanted that x to be, imported from the module or a local variable. If he chose to declare it locally, then at that point there would no implicitly declared variables remaining, and the implicit none could be removed and the code would thereafter compile correctly. If instead the programmer realized that he wanted to import that variable, then one option would have been to add x and y to the only: clause. Simply doing that, with no change to the implicit mapping, would have resulted in a compile time error (barring the above mentioned compiler error). Namely the compiler would have recognized that x could not be imported due to its private attribute within its module.