Skip to content

Variants

IllidanS4 edited this page Apr 12, 2019 · 6 revisions

Introduction

A variant is an object able to store values of multiple "types". When such an object is constructed from a value, not only is the numerical value stored in the object, but also the tag of the value as specified using Pawn:

new Variant:v = var_new(3.14);
assert var_tagof(v) == (tagof(Float:));

Using variants, functions can accept a value of any kind and decide how to process it based on the information stored within the object. Functions like var_sizeof and var_tagof are equivalent to the similarly-named operators in Pawn. As implied by the presence of var_sizeof, variants are also able to store single-dimensional arrays:

new Variant:v = var_new_arr(Float:{1.1, 2.2, 3.3});
assert var_tagof(v) == (tagof(Float:));
assert var_sizeof(v) == 3;
assert Float:var_get(v, 0) == 1.1;
assert Float:var_get(v, 1) == 2.2;
assert Float:var_get(v, 2) == 3.3;

var_new_arr automatically determines the size and tag of the array and copies its data into the variant.

The value inside the variant can be obtained using two methods. The first one, shown above, is type-unsafe. Calling var_get and retagging the value works if the tag of the variant is known beforehand, but assuming the tag may cause potential bugs in the code. Instead, the type-safe equivalent of this function is recommended:

new Variant:v1 = var_new(true);
new Variant:v2 = var_new(-1.0);
new c, bool:b, Float:f;
assert var_get_safe(v1, c) && c == 1;
assert var_get_safe(v1, b) && b == true;
assert var_get_safe(v2, f) && f == -1.0;
assert !var_get_safe(v2, c);

var_get_safe compares the tag of the variant with the tag of the variable, and obtains the value if it deems it compatible. The are two compatibility rules: weakly tagged value (with a tag not starting by an uppercase letter) can be stored in an untagged variable, and String and Variant can be stored in ConstString and ConstVariant, respectively.

In Pawn, variants are passed by reference. This means that a function accepting a variant may potentially cause side effects by modifying the value inside the variant. To prevent unintentional modifying of the value, non-array variants are immutable. However, it is possible to change the content of array variants, but not the tag or structure:

new Variant:v = var_new_arr({1, 2, 3});
var_set_cell(v, 1, 55); //or type-safe var_set_cell_safe

Operations

For easier usage, variants support standard operators and treat them as if applied on their values. This includes arithmetical operators and ==:

new Variant:v = var_new(12);
v *= var_new(16);
assert v == var_new(192);

In the current version, variants must have compatible types in order to successfully use any operators on them. It is not possible to multiply a float variant by an integer variant and so on.

If the variants store a reference to a dynamic string or another variant, their value will be inspected when the variants are compared. To prevent this behaviour, you can tag the values Ref<String> or Ref<Variant> to signalize that only the references should be compared. However, this changes the tag of the value, and so if you want to obtain the value, you will have to obtain it as the correct tag.

new Variant:a = var_new(@("str"));
new Variant:b = var_new(@("str"));
assert a == b;
new Variant:c = var_new(Ref<String>:@("str"));
new Variant:d = var_new(Ref<String>:@("str"));
assert c != d;

Operators also work on array variants, if they have compatible tags and sizes. The method is the same regardless of the operator: elements in both arrays are paired and the operation is performed on all pairs, producing a new array of the same size.

new String:arr[3];
arr[0] = @("a"), arr[1] = @("b"), arr[2] = @("c");
new Variant:v = var_new_arr(arr);
v += var_new(@("x"));
assert str_val(v) == @("({ax, bx, cx})");

In this example, x is appended to all strings in the array.

Lifetime and garbage collection

Variants are garbage collected in the same way as dynamic strings and iterators. Newly created variants exist only temporarily, unless var_aquire is used. Then, when the variant is to be deleted, use var_release. See this for more information.

Multidimensional variants

2D and 3D arrays can be used to construct a variant in addition to the standard array constructor:

new arr1[2][2];
new arr2[3][3][3];
new Variant:v1 = var_new_arr_2d(arr1);
new Variant:v2 = var_new_arr_3d(arr2);

It is possible to use an array of indeterminate size in construction of multidimensional variants, but doing so may result in the last element having incorrect size, since Pawn doesn't provide this information in multidimensional arrays.

For accessing multidimensional arrays stored in variants (and iterators), a whole new set of function exists that accepts an offset array instead of a single offset:

new arr[2][2];
new arr[2][2];
new Variant:v = var_new_arr_2d(arr);
var_set_cell_md(v, {1, 0}, 1);
var_set_cell_md(v, {1, 1}, 2);
new arr2[2];
var_get_md_arr(v, {1}, arr2);
assert arr2[0] == 1 && arr2[1] == 2;

The multidimensional functions can also be used to extract a subarray from a normal array:

new Variant:v = var_new_arr("abc_def");
new arr[4];
var_get_md_arr(v, {4}, arr);
assert !strcmp(arr, "def"); 

Types of variants

A variant can be constructed from four different kinds of values: a single cell (with an arbitrary tag), an array (with an arbitrary tag, any dimension), a string (untagged array), or another variant.

There is a difference between a variant constructed from an array and a variant constructed from a string. Internally, a string variant has a special char tag which identifies it and makes it incompatible with regular arrays. Since char is a keyword in Pawn (used for char arrays), it is impossible to tag any value with it. However, it is possible to take advantage of the tag compatibility rules and tag an array with any subtag of char, making it being treated as a string. If tagged this way, even single values are treated as characters:

assert var_new_str("test") != var_new_arr("test");
assert var_new_str("test") == var_new_arr(char@:"test");
assert str_val(var_new(char@:'A')) == @("(A)");

There is also a special type of a variant, the null variant, represented by VAR_NULL, which has zero size and cannot be deleted. Using this variant is useful for representing "empty" values in containers.

A variant of zero size can be created from an array, but for the purpose of comparison, all zero-length variants are equal.