Skip to content
Home » Blog » By Value, By Reference

By Value, By Reference

This is about how function and subroutine parameters are passed in LotusScript, and how to use that intentionally in consideration of code maintainability and performance.

For basic types

There are two ways a caller can pass a parameter to a subroutine or function. A call “by value” gives the subroutine a copy of the value. A call “by reference” gives the subroutine access to the caller’s variable, so it can change the value of that variable.

Here’s an example:

Sub fie(a%, b%, ByVal c%)
	a = 11
	b = 12
	c = 13
End Sub
Sub Initialize
	Dim x%, y%, z%
	x = 1
	y = 2
	z = 3
	fie x, (y), z
	Print "x=" & x & ", y=" & y & ", z=" & z
End Sub

The output of this code is x=11, y=2, z=3. The fie subroutine assigned its variable a, and that affected the value of the variable the caller passed in as the value of a, the caller’s variable x. The meaning of a “by reference” call is that the variables x and a are different names for the same place in memory.

When fie assigns b and c, it affects only its copies. But a isn’t a copy, so it assigns x in the caller also.

The ByVal keyword on a parameter says we never want that to happen. Because c is specified as ByVal, it will always be a copy of the value passed in. Subroutine fie modifies the value of c, but c is just a copy, so the value of the variable z, which was passed in, is unaffected.

What about y and b? ByVal wasn’t used here, but the value of y was also unaffected. That’s because the second argument to the subroutine call wasn’t y, the variable. It was (y) — an expression. This would be more obvious if it were a different expression, e.g. 5 or y-7. But the parenthesis are enough to force a pass by value.

Accidentally by value

You might inadvertently force a pass by value by conflating the two ways to call a subroutine. The two syntax alternatives for a call are:

Call subname(arg1, arg2...)

or

subname arg1, arg2...

You must either use Call and a parenthesized argument list, or leave off both Call and the parens. Just including one of these would be invalid syntax:

subname(arg1, arg2) ' illegal

But if a subroutine has only one argument, people sometimes (mistakenly) write:

subname(arg1) ' valid syntax but does it do what you intended?

This looks like the list of parameters is enclosed in parenthesis, but because there’s no Call, it’s actually the second syntax, where the argument list isn’t parenthesized. These parentheses don’t enclose the list of arguments. Instead, they’re part of the argument, making it an expression rather than just a variable — just as I did earlier with the argument (y). As in that case, the subroutine receives a copy of the value.

Arrays and lists

Array and list variables can only be passed by reference. I’ve been trying to trick the runtime into passing them by value, e.g. by declaring things as Variant, and that doesn’t work.

However, they can be function return values, and the entire array or list can be copied using an assignment statement, so whatever. It’s probably better not to be passing large and complex data by value anyway, for performance reasons.

Types

Type variables can’t be passed by value. Just like arrays — they can be copied in an assignment or be a return value.

Object variables

Objects can only be passed by reference. In one sense this is obvious because an object variable isn’t where the data of the object is stored — the variable is just a pointer to the object in memory, so when you write:

lastName = myLastName
Set obja = objb

lastName gets a copy of the value in myLastName, but obja isn’t a copy of objb — you just made an additional reference to the same object. The variables obja and objb are pointers to that common memory.

So in that sense every time you pass an object to a subroutine, it’s by reference. But it goes farther than that. LotusScript syntax doesn’t even let you pass the pointer by value. If you try this:

Sub something(ByVal dt As NotesDateTime) ' illegal

the compiler won’t let you. Putting parens around the name in the Call statement as we did with variable y above, also doesn’t work. Whenever you pass an object, you’re really passing a pointer to the pointer variable, and any change the function makes to it, will be reflected in the caller’s variable also.

The caller can protect its local object variable from being changed to point to another object, only by explicitly making a copy of the variable.

Dim obj1 As Coordinate, obj2 As Coordinate
...
Set obj2 = obj1
Call subby(obj2)

The subroutine now can no longer create a new object and make obj1 point to that object. It can only make obj2 point to the new object.

Of course, we’ve only copied the pointer to the object obj1. The subroutine can still modify the contents of obj1. Or it can Delete the object, which would set obj1 to Nothing. Pass by value doesn’t mean a lot with objects, even if it were supported.

If you did want to make a copy of an object to have as a spare to pass to functions and not care what happens to it, you can do that, but you’d need a method to clone the object, and that would be specific to the class. Generally, though, you just work with the one copy.

Applying this knowledge

Knowing all this, what do we do with it?

The area of application is limited — passing by value is only a meaningful thing for simple types like number and string.

Where it is allowed, I use ByVal routinely on arguments that aren’t outputs of the subroutine or function. A developer calling this function doesn’t need to worry about whether the function will modify the value of a variable it was passed, because it can’t. It’s also nice for the person writing the function, because they can modify the value internally without worrying about affecting anything. So if the argument is a folder path (let’s say) and in the function we have a standard that all folder paths have a path delimiter at the end, the function can start out by correcting its input argument, adding the delimiter if needed, without the complexity of defining an extra variable to store the corrected value.

The occasional exception is large values — long strings, for instance — because I don’t want the performance cost of copying a large value when I could just pass in a pointer. The caller will just have to trust (and I’ll have to take care) I won’t modify the contents — the function’s header comment would identify if a parameter is treated an output.

Note: I wish the content assist would identify which arguments are by value, but it doesn’t — you have to do it through documentation.

Leave a Reply

Your email address will not be published. Required fields are marked *