To: J3 J3/21-163 From: Zach Jibben Subject: Readonly components: specifications and syntax Date: 2021-June-25 Reference: 20-121 20-106 19-214r1 19-161 19-135r1 18-265 1. Introduction =============== This paper contains the formal specifications & syntax for readonly components. This supersedes the specifications outlined by previous papers, as subgroup discussion revealed a more flexible feature set was needed to meet competing user requirements. Previous protected-components specifications fell into one of two camps. Papers 18-265 and 20-106 envisioned components inaccessible for *direct* modification outside the module in which the parent type was defined, but did allow a type containing a protected potential subobject to appear in a variable-definition context. These papers propose an access specifier roughly in-between PUBLIC and PRIVATE, by effectively prohibiting the name of a protected component from appearing in a variable-definition context, not protecting the variable itself. Other papers, 19-135r1, 19-161, 19-214r1, and 20-121 offered stronger protection, protecting variables themselves from being modified by virtually any means outside the module where that component was defined. This paper--and its partner paper--aims to satisfy both parties by teasing apart the competing goals into two separate features: readonly components, and restricted types. Readonly components provide an access specification allowing code outside the module defining the type to read, but not *directly* modify components. These components will not impose any restrictions on the type containing them, beyond anything that might be imposed by the analogous PRIVATE or PUBLIC access specifiers. Restricted types may be used to protect the data from appearing in variable-definition contexts. Although this is a formal syntax paper, the syntax will be defined by prose and by example, not by BNF, to aid comprehension. 2. Use Cases ============ Use cases have been presented in previous papers (18-265, 20-106, 19-214r1). Here is a summary; for more, refer to those papers. Readonly components are useful in any situation where a user would want a derived type to expose a large dataset for reading, but does not want the client to modify the data except through provided procedures. They may also want the client to be able to store copies of these objects or deallocate them, while still controlling their internal consistency. Furthermore, a "getter" routine would require an expensive copy of the data, thus is suboptimal. Readonly components allow the programmer to design a derived type API which reflects the intended use of certain objects -- exposing components without also giving the ability to modify them directly. For example, a CFD application's mesh may contain a large connectivity dataset: type :: mesh integer, allocatable, readonly :: nbr(:) contains procedure :: init end type mesh A client must be able to create and initialize this mesh object through provided procedures. They might need copies to preserve some history. But the client absolutely must not be able to modify the nbr component directly. To summarize: subroutine ex1() type(mesh) :: m ! local variables allowed type(mesh), allocatable :: history(:) ! local allocatables allowed call m%init() ! modifying through module routines allowed print *, m%nbr ! reading readonly components allowed allocate(history(1)) ! local allocation allowed history(1) = m ! intrinsic assignment allowed call read(m%nbr) ! passing readonly component as intent(in) allowed call new_mesh(m) ! passing object as intent(out) or intent(inout) ! allowed m%nbr(1) = huge(1) ! FAIL: modifying readonly component not allowed call change(m%nbr(1)) ! FAIL: intent(out) and intent(inout) not ! allowed on readonly components m = mesh(nbr = [huge(1)]) ! FAIL: setting readonly component ! via structure constructor not allowed allocate(m%nbr(1)) ! FAIL deallocate(m%nbr) ! FAIL ! automatic deallocation & finalization allowed ! even without a user-defined finalizer end subroutine ex1 3. Specifications ================= To help keep a record of how specs have changed and to aid discussion, specification labels are preserved from papers 20-121 and 19-214r1. B. The name of a readonly component shall not appear in a variable-definition context, except within the module wherein its type is defined. G. A readonly component or a subobject of a readonly component can only be argument associated with an INTENT(IN) dummy, even if the referenced procedure is in the module in which the type is defined. H. A readonly component or subobject of a readonly component cannot be the target in a pointer assignment outside the module, as that would lose the protection. - Here I think it would be valuable to somehow expose an immutable reference, which could tie into the const-pointer proposals. I. A readonly component or subobject of a readonly component cannot be explicitly allocated or deallocated except within the module in which the type is defined. - Otherwise it's too easy to bypass the protection. - Allocating/deallocating a type containing a readonly component is still permitted, provided it's not subject to some other rule. - Automatic deallocation on scope exit will occur as it would for any non-readonly component. J1. A dummy variable of a type with a readonly component can be INTENT(IN) or INTENT(INOUT) or INTENT(OUT) in any procedure anywhere, unless it's subject to some other rule. L1. No intrinsic assignment to a readonly component or subobject thereof, outside the module in which the readonly component is defined. (cf. 19-135r1) - This does not prohibit intrinsic assignment to a type containing a readonly component. It is only meant to prevent the readonly component from being modified by name. M1. A function defined outside the module may have a result variable of a type with a readonly component. N. A structure constructor outside the module in which the type is defined is allowed if and only if no value is supplied for any readonly component (otherwise this would subvert the module's control over what values are acceptable in the readonly component). - This describes the same user-facing behavior for READONLY and PRIVATE components. However, the logic in the standard must be different. C7102 and 7.5.4.8p2 prevent a structure constructor from specifying a private component by restricting access to the name. For a READONLY component, the name is accessible, yet we still prohibit setting the value of such a component outside the module where the parent type is defined. P1. Readonly components are inherited through type extension. - In this regard, READONLY is like PUBLIC, not PRIVATE. - This is chosen because a readonly component is visible, like a PUBLIC component, and thus is part of the API provided by a parent type. R. Type-wide default readonly status, like PRIVATE, is provided. Default is PUBLIC, unchanged from the current standard. READONLY statement changes the default. Attributes in a component definition stmt confirm or override the default. U. SEQUENCE and BIND(C) types shall not have readonly components. - This includes any level of component selection, because non-SEQUENCE types are not allowed in SEQUENCE, and similarly BIND(C)). AA. A component may be READONLY or PRIVATE or PUBLIC, but not a combination of more than one of these attributes. 4. Syntax ========= One may add the READONLY attribute to component definitions. type :: foo real, readonly :: a real, private :: b real, public :: c end type foo The requirement R allows a type-wide READONLY status, which may be overridden for an individual component using another access specifier. type :: foo readonly real, private :: a real :: b end type foo 5. Comments =========== 5.1 Alternative keywords straw votes There have been many keyword suggestions for this feature. Which should we go with? KEYWORD-1: READONLY (YES/NO/UNDECIDED) KEYWORD-2: PROTECTED (YES/NO/UNDECIDED) KEYWORD-3: LIMITED (YES/NO/UNDECIDED) KEYWORD-4: VISIBLE (YES/NO/UNDECIDED) 5.2 Shouldn't we allow both readonly+private and readonly+public together? Previous incarnations of "protected" components behaved differently than what is proposed here. The presence of one of those protected components in a type definition would also enforce all the restrictions of a restricted type on the entire object. Hence, a component which was both protected and private would have the useful (or onerous) side-effect of restricting the type. In the current form of readonly components, the side-effects are moved to an explicit choice in the form of restricted types. Thus there is no longer a practical benefit to combining private+readonly specifiers. However there's a philosophical argument for making "readonly" a choice independent of "public/private": "public/private" control access to the name, while "readonly" controls the ability to "writability" of the component. But since there's no practical benefit, I advocate three mutually-exclusive options: private, readonly, and public. Straw vote: Should readonly be used in combination with public/private, or should it be a third mutually exclusive option? (mutually exclusive / in combination / undecided) ===END===