Guru: Dynamic Arrays Come To RPG – Limitations, Circumventions, And More
October 12, 2020 Jon Paris
In my first two tips in this series I covered the basics of automatic sizing arrays and variable sized arrays. In this final part am going to discuss some of the current limitations in this support and the facilities IBM has put in place to help circumvent them.
IBM publishes an extensive list of the limitations, but in normal usage only a couple present “real” restrictions in practical terms. If you are curious though, you can find the full list here.
The first limitation to bear in mind is that, currently, only top-level variables can be defined with DIM(*VAR) or DIM(*AUTO). That is to say Data Structure (DS) arrays or Standalone arrays. So an array within a DS cannot be variable in length. This makes sense if you think about it since it would cause any fields that follow the array in the DS to “move” as the length of the array expanded and contracted.
The second, and more significant, limitation is that a variable length array cannot be specified on a prototype, either as a parameter or as a return value. This is not to say that such arrays cannot be passed as parameters. They can, as you will see shortly, but they cannot be defined as such on prototypes. The primary reason for this is that RPG’s dynamic arrays have no direct equivalent in other ILE languages and therefore there is no formal IBM i mechanism for passing the current length along with the array’s data.
IBM have stated that they may add support for these features in the future but I’m not going to hold my breath. I suspect their implementation will depend on how much use RPGers make of the capability and whether they take the time to write up RFEs to demand such enhancements.
Passing Variable Arrays as Parameters
In order to pass a varying dimension array as a parameter, it must be specified on the prototype as an ordinary array, i.e. with a regular DIM(nn) keyword. In addition OPTIONS(*VARSIZE) will be required so that the compiler will not get upset about the fact that the initial size of the array is zero elements. Note that since only the array data is passed, it will normally be necessary for you to pass the current active length to the called routine as a separate parameter so that it can identify how many of the elements are active.
Let’s first look at a simple example of a calling program that passes a variable array as a parameter. We’ll look at the called program shortly. Note that this program is just a test harness that will ask for the number of elements to fill, generate that test data, and then pass the resulting array to a second program.
(A) We begin by defining the array as being Automatic sized (*AUTO) with a maximum number of elements of 100.
(B) Note that when defining the array on the prototype I specified the dimension as being 100 (the maximum that it could be) and that the *Varsize option must be specified so that the compiler will accept an array smaller than the maximum (in other words, if our array contains fewer than 100 elements.)
(C) The program now asks how many elements to load and then (D) checks the number requested against the maximum that the array can hold, substituting the maximum for the requested number if needed.
Note that although I chose to hard code the array size in the prototype definition, there is a %ELEM option that I could have been used to set the array size. This would make the program more flexible. So instead of coding DIM(100) I could have used DIM( %ELEM( dynarray : *MAX ).
At (E) I then simply store generated values in the requested number of elements before calling the DYNARRAY6C program which will display the array contents.
Notice that when the program is called the current active element count (i.e. %ELEM) is passed as a parameter so that the operation of the called program can be limited to the active elements.
(A) dcl-s dynArray int(5) Dim( *Auto : 100 ); dcl-s count int(5); dcl-s i int(5); dcl-s wait char(1); dcl-pr DynArray6C ExtPgm; (B) array Int(5) Dim(100) Options(*VarSize); elements int(5); End-Pr; (C) Dsply 'How many elements should I load?' ' ' count ; (D) If count > %Elem(dynArray : *max ); // Use max if > max requested count = %Elem(dynArray : *max ); EndIf; (E) // Fill requested number of array elements with values For i = 1 to count; dynArray(i) = i + count; // Add new element EndFor; // Now call program notifying it of current element count (F) DynArray6C ( dynArray: count ); Dsply 'All done! - Hit enter to terminate' ' ' wait;
There is really nothing special about the called program — it receives and processes the array using the second parameter received to avoid accessing data beyond the actual current size of the array. As you can see at (G) the procedure interface mirrors the prototype definition. And yes, I should have been a good boy and had the prototype in a copy member and /copied it in here too but since this is just a teaching example I opted for simplicity.
You can see at (H) that the program uses the count supplied by the caller to limit its access to only those elements that are valid. Who knows what lurks in the storage locations beyond the number of active elements in the passed array!
dcl-s i int(5); (G) dcl-pi DynArray6C ExtPgm; array int(5) Dim(100); elements int(5) Const; End-Pi; Dsply ( 'Received ' + %Char(elements) + ' elements'); Dsply 'Values are:'; (H) For i = 1 to elements; Dsply ('Element ' + %Char(i) + ' has the value ' + %Char(array(i)) ); EndFor;
That’s all there is to it. Or is it? What if the called program needs to add new elements to the array? How could we handle that?
Never fear, the good folks at IBM have thought of this.
Adding Array Entries
There are two things to consider here. First that if the called routine is going to add records to the array, then we need to be sure that there is sufficient memory allocated to the array to allow this. Second, the called routine must notify us of the updated active element count so that we can reset that count on return. Note that this would also have to be done had the called routine deleted array entries – not just added them.
The vast majority of the logic in the two programs is basically the same as in the two programs above, so I am just going to highlight the significant changes.
In the calling program, at (I) I use %ELEM with the *ALLOC option to ensure that sufficient memory has been allocated to the array to contain the maximum number of possible elements. Note that this has no effect on the number of active elements, just to the memory allocation.
After the call I then use %ELEM to set the active element count to accommodate any increase/decrease in the number of elements as determined by the called program. The critical thing to note here is the *KEEP option. It basically prevents RPG from performing any initialization on any new elements. Without this should, for example, the active count go from 10 before the call to 25 after, then RPG would normally initialize elements 11 to 25 to their default values. Not a good idea since all the values just added by the called routine would be nuked. *KEEP tells RPG that you are quite happy with the values currently in those array slots and that they should not be initialized.
// Allocate sufficient space for the maximum elements (I) %Elem(dynArray : *ALLOC ) = %Elem(dynArray : *Max ); // Now call program notifying it of current element count DynArray7C ( dynArray: count ); // Reset active element count preserving new values // that have been added by called program (J) %Elem( dynArray : *KEEP ) = count;
Since the called program doesn’t know the array it was passed is dynamic, there is no special logic needed.
Here I simply loop though all the active elements in the array (K) modifying each entry in turn just to demonstrate that they can be updated.
I then increment the active element count that was passed to me (L) and use this to set the value 12345 in that new element. The value in the element count (the elements field which was passed to this program as a parameter) has been updated to account for the new entry. This is the value that is subsequently used by the caller to reset the active element count back in the calling program above at (J).
(K) For i = 1 to elements; array(i) += i; // Increase value of each element by i EndFor; // Now add an extra element to the array (L) elements += 1; array(elements) = 12345;
I think these new types of arrays are a very useful addition to the RPG language. For those of you who have been programming in RPG for years they may not seem that exciting, but for newcomers to the language they just help to remove one more barrier to RPG being accepted as the excellent business language that it is.
Hopefully over this series I’ve given you a good idea of the way these new types of array work and how to overcome the primary limitation of the current support. If you have any questions or comments please let me know.
Jon Paris is one of the world’s foremost experts on programming on the IBM i platform. A frequent author, forum contributor, and (prior to Covid-19) speaker at User Groups and technical conferences around the world, he is also an IBM Champion and a partner at Partner400 and System i Developer. He hosts the RPG & DB2 Summit twice per year with partners Susan Gantner and Paul Tuohy.