Functions in RPG
Learn how to write reusable code in RPG using subprocedures, subroutines, parameters, return values, scope, and recursion in modern free-form RPG IV
RPG is a procedural language, and it offers two distinct mechanisms for organizing reusable code: subroutines and subprocedures. Understanding the difference between them is central to writing modern RPG, because they represent two eras of the language’s evolution.
Subroutines (BEGSR/ENDSR) are the older mechanism, inherited from RPG II and RPG III. They take no parameters, return no value, and share all of the program’s variables - they are essentially named blocks of code you jump to with EXSR. Subprocedures (DCL-PROC/END-PROC) arrived with RPG IV and the Integrated Language Environment (ILE) in 1994. They are true functions: they accept typed parameters, return a value, and have their own local variable scope.
In modern RPG development, subprocedures are strongly preferred because they support proper encapsulation, recursion, and reuse across programs. This tutorial uses free-form RPG IV (**FREE) throughout and covers both mechanisms, along with parameters, return values, scope, and recursion. Because RPG compiles and runs only on IBM i, the examples include IBM i compilation steps rather than Docker - there is no open-source RPG compiler.
Subprocedures: The RPG Function
A subprocedure is defined between dcl-proc and end-proc. Its parameters and return type are declared in a procedure interface (dcl-pi / end-pi). The ctl-opt main(...) keyword names the entry-point procedure, replacing the older program cycle.
Create a file named functions_basic.rpgle:
**FREE
ctl-opt main(main);
dcl-proc main;
dcl-s result packed(7:0);
dcl-s msg char(50);
result = addNumbers(15 : 27);
msg = 'Sum = ' + %char(result);
dsply msg;
end-proc;
// A subprocedure that takes two parameters and returns a value
dcl-proc addNumbers;
dcl-pi *n packed(7:0); // *n means an unnamed (anonymous) interface
a packed(7:0) const; // const = read-only, passed efficiently
b packed(7:0) const;
end-pi;
return a + b;
end-proc;
Key points:
dcl-pi *n packed(7:0)declares the procedure interface. The*nis a placeholder name, andpacked(7:0)after it is the return type.- Each line inside
dcl-pi/end-piis a parameter with a name and type. constmarks a parameter as read-only. It lets the compiler pass the argument efficiently and accept literals or expressions (like15and27).- Parameters are separated by colons in the call:
addNumbers(15 : 27).
Variable Scope: Local vs Global
Subprocedures introduce true local scope. Variables declared inside a dcl-proc exist only within that procedure. Variables declared at the top of the source (outside any procedure) are global and visible everywhere.
Create a file named functions_scope.rpgle:
**FREE
ctl-opt main(main);
dcl-s globalCount int(10) inz(0); // global - shared by all procedures
dcl-proc main;
dcl-s msg char(50);
increment();
increment();
increment();
msg = 'Global count = ' + %char(globalCount);
dsply msg;
end-proc;
dcl-proc increment;
dcl-s localTemp int(10); // local - re-created on every call
localTemp = globalCount + 1;
globalCount = localTemp; // global persists between calls
end-proc;
Each call to increment gets a fresh localTemp, but they all read and write the same globalCount. After three calls, the global value is 3. Favoring parameters and local variables over globals is a hallmark of well-structured modern RPG.
Recursion
Because each subprocedure call gets its own copy of its local variables and parameters, subprocedures can call themselves. This makes recursion straightforward - something the old subroutine model could not do safely.
Create a file named functions_recursion.rpgle:
**FREE
ctl-opt main(main);
dcl-proc main;
dcl-s n int(10) inz(5);
dcl-s msg char(50);
msg = %char(n) + '! = ' + %char(factorial(n));
dsply msg;
end-proc;
// Classic recursive factorial
dcl-proc factorial;
dcl-pi *n int(20);
num int(10) const;
end-pi;
if num <= 1;
return 1;
endif;
return num * factorial(num - 1);
end-proc;
The procedure calls itself with a smaller value until it reaches the base case (num <= 1). The return type is int(20) so it can hold large factorials. factorial(5) evaluates to 5 * 4 * 3 * 2 * 1 = 120.
Subroutines: The Traditional Approach
Subroutines predate subprocedures and remain common in legacy code. A subroutine is defined with begsr / endsr and invoked with exsr. It takes no parameters, returns no value, and shares all of the program’s variables. This example uses the classic program-cycle style (no ctl-opt main), ending with *inlr = *on.
Create a file named functions_subroutine.rpgle:
**FREE
dcl-s price packed(9:2) inz(100.00);
dcl-s taxRate packed(5:4) inz(0.0825);
dcl-s taxAmount packed(9:2);
dcl-s total packed(9:2);
dcl-s msg char(50);
exsr calcTax; // execute the subroutine
msg = 'Total = ' + %char(total);
dsply msg;
*inlr = *on;
// Subroutine: operates directly on the program's variables
begsr calcTax;
taxAmount = price * taxRate; // 100.00 * 0.0825 = 8.25
total = price + taxAmount; // 108.25
endsr;
Notice there is no parameter passing - calcTax reads price and taxRate and writes total because all three are shared program variables. This implicit data sharing is convenient for small programs but makes subroutines harder to reuse and reason about than subprocedures.
Passing by Reference and Built-in Functions
By default, RPG passes parameters by reference, so a subprocedure can modify the caller’s variable. Omitting const (and any return type) creates a procedure that works like a function with output side effects. This example also uses the built-in function %xlate to convert text to uppercase.
Create a file named functions_byref.rpgle:
**FREE
ctl-opt main(main);
dcl-proc main;
dcl-s text varchar(50) inz('hello world');
dcl-s msg char(60);
toUpper(text); // modifies 'text' in place
msg = 'Result: ' + text;
dsply msg;
end-proc;
// No return type and no const: parameter is updated by reference
dcl-proc toUpper;
dcl-pi *n;
value varchar(50); // changes here are visible to the caller
end-pi;
value = %xlate('abcdefghijklmnopqrstuvwxyz':
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' : value);
end-proc;
%xlate(from : to : string) replaces each character found in the from string with the character at the same position in the to string. After the call, the caller’s text variable holds 'HELLO WORLD'. RPG also offers const (read-only) and value (pass-by-value, a private copy) for tighter control over how arguments are passed.
Running on IBM i
RPG requires an IBM i system - there is no Docker image or open-source compiler. On IBM i, compile and run a program with CL (Control Language) commands. For example, to build and call functions_basic.rpgle:
| |
Repeat the same pattern for each source file, changing the program name and SRCSTMF path. If you do not have IBM i access, PUB400.COM offers a free public system for learning, and IBM Power Virtual Server provides cloud-based IBM i instances.
Expected Output
Each program displays a single message via DSPLY:
functions_basic.rpgle -> Sum = 42
functions_scope.rpgle -> Global count = 3
functions_recursion.rpgle -> 5! = 120
functions_subroutine.rpgle -> Total = 108.25
functions_byref.rpgle -> Result: HELLO WORLD
On a 5250 terminal, each DSPLY message waits for the user to press Enter. In batch mode, the messages appear in the job log prefixed with DSPLY.
Key Concepts
- Subprocedures (
dcl-proc/end-proc) are RPG’s true functions: typed parameters, a return value, and local scope. Prefer them for all new code. - Subroutines (
begsr/endsr, called withexsr) are the older mechanism - no parameters, no return value, and they share all program variables. - The procedure interface (
dcl-pi/end-pi) declares parameters and the return type; the type after the*ninterface name is the return type. - Parameters default to pass-by-reference, so changes are visible to the caller. Use
constfor read-only arguments andvaluefor a private copy. ctl-opt main(name)designates an entry-point procedure, replacing the implicit program cycle used by older*inlr = *onprograms.- Local variables declared inside a procedure are private and re-created on every call, which is what makes recursion safe in subprocedures.
- Built-in functions (BIFs) like
%charand%xlatecomplement your own procedures and always start with%.
Comments
Loading comments...
Leave a Comment