On occasion, it might be more practical to have a function return a pointer to a RECORD or an ARRAY, rather than the data item itself. Indeed, early versions of Modula-2 did not allow a function procedure to return a structured type, and a work-around returning a pointer was often necessary. More commonly, a library module might implement an ADT as a pointer, and its function procedures that return this type will be returning pointers. (As shall be seen in succeeding sections, this is in fact the standard way of working). To illustrate some of the points relating to such methods, the following module provides a partial implementation of a dynamic (but fixed length) string type, together with a function procedure that reads a string and returns a pointer to the string. There is a brief testing harness at the end.
NOTE: The choice of ReadString to do the assignment is for the sake of illustration only. The analysis and comments apply to any procedure that is to assign something to dynamic memory and communicate back to a caller.
MODULE DynStringDemo; (* A crude demonstration of pointer return styles by R. Sutcliffe 1995 05 17 *) FROM STextIO IMPORT WriteString, WriteLn, SkipLine, ReadString; FROM SIOResult IMPORT ReadResult, ReadResults; FROM Storage IMPORT ALLOCATE; CONST maxChars = 80; TYPE String = ARRAY [0..maxChars-1] OF CHAR; StrPoint = POINTER TO String; VAR sPoint : StrPoint; PROCEDURE ReadDynStr1 () : StrPoint; VAR answer : StrPoint; BEGIN NEW (answer); ReadString (answer^); SkipLine; RETURN answer END ReadDynStr1; BEGIN WriteString ("type string1 here=> "); sPoint := ReadDynStr1 (); WriteLn; WriteString (sPoint^); WriteLn; END DynStringDemo.
In place of the first two lines of the main block, one might be tempted to reason that since the procedure ReadDynStr1 returns a pointer to an item that is the correct type for the parameter to ReadString one could write:
WriteString (ReadDynStr1 ()^);
However, this is not the case in Modula-2. The rule is:
Modula-2 function procedures cannot be selected. It is illegal to write:
Proc (params)^
Proc (params) [index]
Proc (params).fieldname
This style of working may be adequate for some purposes; however there may also be some difficulties. Repeated calls to a procedure that must create a new dynamic string in order to have something to return might litter up the memory quote unnecessarily. Moreover, the creation of a new dynamic entity has been combined with the act of reading one, and this seems rather unstylish. One possible modification would be to separate the creation of the dynamic entity from the reading of one by having two procedures.
PROCEDURE MakeDynStr () : StrPoint; VAR answer : StrPoint; BEGIN NEW (answer); RETURN answer; END MakeDynStr; PROCEDURE ReadDynStr2 () : StrPoint; VAR answer : StrPoint; BEGIN ReadString (answer^); SkipLine; RETURN answer END ReadDynStr2;
One would call them as follows:
WriteString ("type string2 here=> "); sPoint := MakeDynStr (); sPoint := ReadDynStr2 (); WriteLn; WriteString ( sPoint^); WriteLn;
However, this style too has its disadvantages, for it requires the use of a variable global to both procedures, a solution that is sure to be unsatisfactory in a library module. Once such a global variable had its contents passed back to a program, the next time the procedure was called it would alter that global variable, and the calling program could be thereby invalidated, for whatever one of its pointer variables had been pointed at the global item would now also be altered.
Perhaps a more satisfactory style to use when it is necessary to return a pointer to a calling procedure is the variable parameter:
PROCEDURE ReadDynStr3 (VAR strPoint: StrPoint); BEGIN ReadString (strPoint^); SkipLine; END ReadDynStr3;
Here, it is the responsibility of the caller to provide the (static) memory for the pointer variable, and to allocate memory to which it can point. The function procedure accesses this via a variable parameter and assigns directly into the supplied memory; it does not obtain any more of its own. This one would be called in the more conventional style shown below (where the call to NEW with sPoint has already been made):
WriteString ("type string3 here=> "); ReadDynStr3 (sPoint); WriteLn; WriteString (sPoint^); WriteLn;
It may be easier to declare a local variable in the procedure to hold the data temporarily and then pass this to the space pointed to by the variable parameter; this is very little different from the third variation above and is called the same way.
PROCEDURE ReadDynStr4 (VAR strPoint: StrPoint); VAR result : String; BEGIN ReadString (result); SkipLine; strPoint^ := result; END ReadDynStr4;