One of the great things about working on VoltScript with our team are the regular discussions about the inner workings of the language. Of course it’s also nice how easy it is to write and run a test script with the language, to quickly test various scenarios. Recently, because of two separate initiatives we’ve been working on, the topic of conversation has been Variants, and the potential values that denote a variant without a value.
Understanding a Variant
Firstly, it’s important to note that the Variant datatype is a catch-all variable datatype designed to hold any type of “thing”. These different “things” are:
- scalars - strings, numerics, booleans, bytes.
- objects - instances of LSX classes or custom classes.
- arrays - indexed collections of scalars, objects, or both.
This explains certain language functions that can be run against Variants:
isScalar(), returning true for scalars.
isObject(), returning true for objects.
isArray(), returning true for arrays.
But there are three other “values” that a variant can have: EMPTY, NULL or Nothing. The explanation for why these exist comes down to those first two types of “things” a variant can contain.
When you create a Variant, it has a default value - EMPTY. This can be seen with the following code:
Dim foo as Variant Print TypeName(foo) 'Prints EMPTY Print IsEmpty(foo) 'Prints true
It’s not possible to explicitly set a variable to EMPTY -
foo = EMPTY will not compile. And you can’t “empty” a variant. The only way to “reset” a variant is to set it to a variant variable that has never had a value put into it.
IsEmpty() is not the only function that is relevant to the EMPTY datatype. Try the following:
Print isArray(foo) Print isScalar(foo) Print isObject(foo)
It’s not a surprise that the first line prints “False”. What is more important is that the second prints “True” and the third “False”.
Yes, EMPTY is a scalar.
This becomes important when you want to assign it to another variable. As Andre Guirard blogged recently, there are two ways to assign a value to a variable - again, depending what kind of “thing” it is.
Set is the keyword to use for an object.
Let - which is a NoOp and so can be omitted - is the keyword to use for a scalar or array.
The following code will throw a Type Mismatch error on the third line:
Dim foo as Variant Dim bar as Variant Set foo = bar
The mismatch is because we’re trying to allocate a scalar - EMPTY - to a variant
bar that we explicitly state (by using
Set) will be an object.
Nothing, on the contrary, is an object. Try the following:
Dim foo as Variant Set foo = Nothing Print isArray(foo) Print isScalar(foo) Print isObject(foo)
This will print “False”, “False” and “True”. And note particularly the syntax in the second line: we have to use
Set to allocate the datatype Nothing to
foo. Another interesting point is what
Print TypeName(foo) returns: whereas the datatype for an empty variant is “EMPTY”, the datatype for a variant set to Nothing is “OBJECT”.
Picking up from our last piece of code from the EMPTY examples, the following will not compile:
Dim foo as Variant Dim bar as Variant Set foo = Nothing bar = foo
This doesn’t generate a run-time error but a compile-time error, because our code has told the compiler
foo will be an object. The following code, however, won’t generate a compile-time error.
Dim bar as Variant bar = getFoo() Function getFoo() as Variant Set getFoo = Nothing End Function
That’s because the
getFoo() function returns a variant, but a variant can be a scalar or an object. So instead it will return a runtime error: SET required on class instance assignment.
Now we come to the interesting part. EMPTY is not the same as “nothing”. Instead it means “not initialized”.
This becomes crucial when you’re allocating a variant to a variable that is not a variant. We know that variables are initialized with a value. A string has an initial value “”. A numeric has an initial value 0.
Dim foo as Variant Dim fooInt as Integer Dim fooStr as String fooInt = 12 fooStr= "12" fooInt = foo fooStr = foo Print fooInt Print fooStr
The first print statement will print “0”. The second print statement will print “”. Setting it to the “not initialized” value resets the Integer and the String to their default values.
But what if a blank string or 0 means something else. What if you want to return potentially a string or integer, but also something that’s not a string or an integer. For example, in
This is where NULL comes in, and why we have
IsNull(). And this is also where variants come in.
The following code will work, and print 1:
Dim arr(1) as String Dim fooInt as Integer arr(0) = "Hello" arr(1) = "World" fooInt = ArrayGetIndex(arr, "Hello") Print fooInt
But change the penultimate line to
fooInt = ArrayGetIndex(arr, "Hellos") and you’ll get an error - Invalid use of null. This becomes apparent from the following code:
Dim foo as Variant foo = NULL Print isArray(foo) Print isScalar(foo) Print isObject(foo) Print isNull(foo)
This prints “False”, “False”, “False”, “True”. NULL is not an array (not surprisingly), not a scalar, not an object.
The ArrayGetIndex example can be fixed like so:
Dim arr(1) as String Dim foo as Variant arr(0) = "Hello" arr(1) = "World" foo = ArrayGetIndex(arr, "Hellos") Print foo
IsNull() can be used to test if it’s returned null, but otherwise it will return a variant of datatype “INTEGER”. Thus ArrayGetIndex only needs to be run once, but can be used to check if the value is in the array and get its index, if it does.
But let’s look back at the earlier example with a
Function getFoo() as Variant ... End Function
This function could return a scalar or an object. If the code is looking up something and actually finds something valie, we check if it’s an object and, if it is, use
Set otherwise omit it.
But what if we don’t get something? The calling code knows whether it expects an object or a scalar. But our
getFoo() function doesn’t. I’ll leave that problem with you for now, but it is a problem that generic code needs to handle.