You can't pass an expression as a parameter that is defined as ByRef??? Really?

[quote=128502:@Rick Araujo]Sub doSomething(ByRef a() As Integer, i as Integer)
a(i) = Pow(a(i) + 1, 2) //silly example.
End Sub[/quote]
ByRef is not required here.

Yep. Just for the sake of clarity.

But it’s not clearer. It really muddies things up, just like in the OP.

@Tim Hare - [quote=128501:@Tim Hare]When you declare a variable of an object type, that variable contains a reference to the object, not the object itself. When you pass that variable to a function (without using ByRef), you are passing the reference (a pointer really) ByVal. That means you cannot change the value of the reference - it will still point to the same object when the function returns, no matter what you do in the function. If you pass it ByRef, though, the function may actually change the object instance that the variable refers to. So it does make sense to think about passing an object ByVal or ByRef. There is still a distinction.[/quote]

Thank you Tim - this is enlightening.

I’m afraid the documentation is in error. Have you actually tried it? I have.

Arrays or objects are not passed ByRef unless declared as such. For example, lets assume you have two methods

[code]Private Sub TestByRef(ByRef someArray() as Integer)
someArray = array(100, 101, 102)
End Sub

Private Sub TestByVal(someArray() as Integer)
someArray = array(100, 101, 102)
End Sub[/code]

Then TestByRef will actually replace the array while TestByVal will have no effect at all. If you call these methods like this

[code] dim a(3) as Integer

a = Array(1, 2, 3)

TestByVal a

MsgBox Str(a(0))

TestByRef a

MsgBox Str(a(0))[/code]

The result is 1 for the first call (i.e. a hasn’t changed) but 100 for the second call as a has been assigned a different array (by virtue of being passed ByRef).

Nope. I don’t think so. I am expliciting to the reader: a() can’t be a copy, needs to be a reference to work. But YUMV (Your understanding may vary :slight_smile: )

What happens if:

Private Sub TestByRef(ByRef someArray() as Integer)
someArray(0) = 100
End Sub

Private Sub TestByVal(someArray() as Integer)
someArray(0) = 100
End Sub

I think you reached Tim’s observations of ByRef and new reference copies due to ByVal

For my own clarification, I ran through @Michael Hußmann 's example, paying close attention to the address of a.
When a is first declared, the address was: &hF040AA55. When inside the TestByVal method, before the assignment it is still &hF040AA55. After the assignment, while still inside TestByVal, the address changes to &h8065AA55. When returning back to the original calling context (ie., after returning from TestByVal, the a in the original context is still &hF040AA55. Inside TestByRef, the a starts as still the original &hF040AA55, and after the assignment changed to &hF01E9755. When TestByRef returned, the original a in the the original context had changed to &hF01E9755.

So, regardless of whether I’m passing ByRef or ByVal, if I manipulate the contents of my array a, i.e, a(0) = 100 as Rick suggested, then it will modify the contents of the original a passed into either method.

But when passing ByVal, if I modify a directly (i.e., create a new array) the array in the original context is not changed. That only happens when passing ByRef.

The difference is that when I’m passing byVal, I’m really passing a copy of the original pointer. When passing ByRef, I’m passing the original pointer.

Have I got things straight?

[quote=128514:@Kimball Larsen]The difference is that when I’m passing byVal, I’m really passing a copy of the original pointer. When passing ByRef, I’m passing the original pointer.

Have I got things straight?[/quote]


I didn’t invent ByRef parameters, and I no longer work for Xojo (which was Real Software back then), but perhaps my perspective will be useful as I am the person who wrote the code behind that error message.

First, there word “reference” describes two different language concepts which are similar but not equivalent. With a reference type, like an array or an object, the value of a variable or expression consists of a pointer to the object data. You can pass that value around, and different variables can have copies of that value, but when you call a method or retrieve a property of that object, you’re going to end up in the same place no matter which variable you start from. You could think of an object variable as something which holds the identity of an object.

A reference parameter is a different mechanism, which also happens to be based on pointers. In this case, instead of passing in the value of a variable or expression, the calling function passes in the address of a variable - some specific variable. It is as though the parameter and the original variable are the same - if the callee assigns a new value to the parameter, in that same instant the original variable is updated, because the parameter is bound to the same address as the original variable.

Reference types are about the data inside the object or array, and the fact that different views on the same object will see the same data; reference parameters are about the contents of a variable, and the fact that the calling function’s variable and the called function’s variable share the same memory.

The comment in the documentation that “All arrays and object types (including arrays) are always passed by reference regardless of whether you have specified to use ByVal” is true, I suppose, but it is misleading, because the kind of “reference” implied by “reference type” is different than the kind of reference meant by “reference parameter”.

Now, then. Kimball showed this example, showing how one might implement something similar to ByRef for an array element:

dim numbers() as Integer numbers.append(1) numbers.append(2) numbers.append(3) dim i as integer = numbers(1) doSomething(i) numbers(1) = i

In this model, ByRef guarantees that the original value will be updated by the time the called function returns. But ByRef actually provides a much stronger guarantee: it ensures that the parameter’s value and the original variable’s value will always be the same - there will never be any way of observing an instant when they are not equal, because the parameter will be bound to the original variable’s storage. Consider:

Class RefDemo mSquareCount As Integer ' Square a number and count the number of squaring operations we have performed Sub SquareValue(ByRef someValue As Integer) someValue = someValue * someValue mSquareCount = mSquareCount + 1 End Sub Sub ShowOff() Dim temp As Integer = 1 SquareValue temp ' temp = 1, mSquareCount = 1 temp = 7 SquareValue temp ' temp = 49, mSquareCount = 2 SquareValue mSquareCount ' mSquareCount = 5 End Sub End Class

If ByRef worked by having the caller passing the variable’s current value in, then receiving some new value back out and assigning it to the original variable, we would see a different result: mSquareCount would end up equal to 4, not 5. At the point where SquareValue increments mSquareCount, mSquareCount would still have its original value, 2 - so SquareValue would assign the value 3, then return, and the caller would promptly overwrite this value with the new value assigned to someValue, which is 4. But that’s not how it works: the meaning of ByRef is not that the calling function assigns a new value, but that the parameter is bound to the same piece of memory as the caller’s variable, and therefore everything the called function does to its parameter applies immediately and transparently to the caller’s variable.

This is a very strong guarantee, and this is why the compiler is so particular about which kinds of variables can be passed as a ByRef parameter. It is not enough that the calling function can locate storage for the variable; the calling function must also be able to guarantee that this storage will continue to exist as long as the called function is running, no matter what the called function does with respect to other objects or global state, and it must be able to guarantee that the item will not move.

In practical terms, the only kinds of variables which satisfy this constraint are local variables, because the calling function owns them and knows they will only be finalized on its return, which cannot happen until the called function returns; object member variables, because the calling function knows that it holds a reference to its “self”, which therefore cannot be released until it returns, at soonest; and properties of modules, whose lifetime is equal to that of the program as a whole.

The called function cannot make this guarantee about properties of other objects, or about elements of arrays, because of the fact that objects and arrays are reference types: their storage lives out in the heap somewhere, and it is subject to change. The calling function cannot know what the called function might do, so it has to assume that it is possible for the called function to manipulate global state in such a way that any possible object might be released or any possible array might be redimmed, thereby invalidating the address of any member which was passed in ByRef.

Now of course you can find all kinds of holes to poke in this, starting with the fact that the array in your example is local, and so it is therefore impossible for any other function to modify it. But how far is the compiler supposed to chase this chain of causality? What if you once passed the array in as a parameter to some other function? That other function might or might not have saved a permanent reference to the array, which some other piece of code might or might not ever use to modify the array’s storage!

This is a potentially endless game, and I chose not to play it. It is easier for everyone if the rules are simple, even if they are occasionally inconvenient, because then at least it is easy to remember what the rules are.

1 Like

When I grow up, I want to be @Mars Saxman.

Awesome explanation. Thank you for the very in-depth info.

I just came back here to click the like button. :slight_smile:

[quote=128509:@Rick Araujo]What happens if:

Private Sub TestByRef(ByRef someArray() as Integer)
someArray(0) = 100
End Sub

Private Sub TestByVal(someArray() as Integer)
someArray(0) = 100
End Sub[/quote]

As in both cases you are passing the same array (just with an additional level of indirection in the first case), not a copy of that array, both methods modify the original array. Only the first method could also replace rather than just modify the array, due to the parameter being passed ByRef, but in this case it doesn’t.