11. Implicit Casts
An implicit cast is a conversion the compiler performs automatically,
with no syntax in the program text. Implicit casts are the counterpart of
the explicit casts written as<toType>(value) in
Type Casting; “cast” is the umbrella term for both.
Most conversions that can be performed implicitly can also be written
explicitly as an as<> cast. Note that a scalar-to-array
explicit cast must state the destination size explicitly
(Scalar to Array), whereas the corresponding implicit cast
takes its size from the array operand. The string / character[*]
conversion, being the array/vector cast specialized to character, has
both an implicit and an explicit as<> form like any other array/vector
cast; see Character Array to/from String.
A scalar may be implicitly cast to an array of any rank (see Scalar to Array). An array is never implicitly cast to a different rank; only a scalar expands to fill an array or matrix, and then only if the dimensions are completely specified.
Attempting any conversion this chapter does not describe as a valid implicit
cast, such as in a declaration, an assignment, or between corresponding tuple
members, is a compile-time error; the compiler must emit a TypeError
(see Errors).
11.1. Scalars
The only automatic implicit cast between scalars is integer to
real. This cast is one way, so a real is never implicitly cast to
integer.
Automatic conversion follows this table where N/A means no implicit cast is
possible, id means no conversion necessary, and as<toType>(value) means the
value of type “From type” is converted to type “toType” using semantics from
Type Casting.
To type |
|||||
From type |
boolean |
character |
integer |
real |
|
boolean |
id |
N/A |
N/A |
N/A |
|
character |
N/A |
id |
N/A |
N/A |
|
integer |
N/A |
N/A |
id |
as<real>(value) |
|
real |
N/A |
N/A |
N/A |
id |
|
Because character and integer are N/A in both directions, there is no
implicit cast between them. A direct consequence is that character values
are not orderable: the relational operators <, >, <=, >=
are undefined on characters, and ordering them requires an explicit
as<integer>(...) cast (see Character and Type Casting).
11.2. Scalar to Array
All scalar types can be implicitly cast to arrays whose element type the scalar can be implicitly cast to. This can occur when an array is used in an operation with a scalar value.
The scalar is implicitly cast to an array matching the array operand’s size (the operand-size rule of Operations); the result’s element type is whichever type the operation requires, and the scalar is first implicitly cast to that element type. For example:
integer i = 1;
integer[*] v = [1, 2, 3, 4, 5];
integer[*] res = v + i;
res -> std_output;
Output
[2 3 4 5 6]
Other examples:
1 == [1, 1] // true
1..2 || 3 // [1, 2, 3]
Concatenation (||) is an exception to the size-matching rule above: a
scalar operand becomes a single new element regardless of the other
operand’s length, rather than being expanded to match it (see
Operations).
Note that an array can never be cast down to a scalar, even explicitly.
Also note that matrix multiply imposes strict requirements on the
dimensionality of the operands. The consequence is that, as an operand of
matrix multiplication (**), a scalar can only be implicitly cast to a
matrix when the other operand is a square matrix (\(m \times m\)): the
scalar is then broadcast (filled) into an \(m \times m\) matrix whose every
element equals the scalar. For higher-rank arrays this generalizes only to
hypercubes with all extents equal; Gazprea provides no comprehensive
broadcasting, so a scalar cannot be broadcast to a non-square matrix operand of
** at all. In element-wise operations and initializations a scalar is
implicitly cast to an array (or matrix) of any dimensions.
11.3. Tuple to Tuple
A tuple may be implicitly cast to another tuple type when the two have an equal
number of members and each member of the source can be implicitly cast to the
corresponding member of the destination. Each member is cast by the rule for
its own kind: scalar members follow the scalar table above, array members
follow the array sizing rules, where
a shorter value is
padded with the element type’s zero value and a longer value raises a
SizeError (see Errors). A nested tuple, vector, or
array member follows the same implicit-cast rules as a standalone value of that
type. A struct member is the exception: a struct is never implicitly
cast (see Structs), so the two struct types must be identical and
the member is copied unchanged. For example:
tuple(integer, integer) int_tup = (1, 2);
tuple(real, real) real_tup = int_tup;
tuple(character, integer, boolean[2]) many_tup = ('a', 1, [true, false]);
tuple(character, real, boolean[2]) other_tup = many_tup;
real_tup.1 -> std_output; '\n' -> std_output;
real_tup.2 -> std_output;
Output
1
2
If initializing a variable with a tuple via Type Inference, the variable is inferred to have the same type as the tuple initializer. Therefore, tuple elements are also copied accordingly. For example:
tuple(real, real) foo = (1, 2);
tuple(real, real) bar = (3, 4);
var baz = foo;
baz.1 -> std_output; '\n' -> std_output; // 1.0
baz.2 -> std_output; '\n' -> std_output; // 2.0
baz = bar;
baz.1 -> std_output; '\n' -> std_output; // 3.0
baz.2 -> std_output; // 4.0
Output
1
2
3
4
It is possible for a two-sided implicit cast to occur with tuples. For example:
boolean b = (1.0, 2) == (1, 2.0); // returns true
b -> std_output;
Output
T
11.4. Array to/from Vector
An array value and a vector are implicitly cast to one another in both directions. Like every implicit cast this converts a value; it never changes how either side is sized. Each element converts by the implicit-cast rule for its own type: the scalar table of Scalars for a scalar element, or the corresponding rule elsewhere in this chapter, applied recursively, for a composite element type.
Vector to array. The vector’s current length produces the array value. Storing that value into an array obeys the array’s own fixed length: a shorter value is padded with the element type’s zero value and a longer value raises a
SizeError(see Errors). If the destination is an inferred[*]array and this is its initialization, the vector’s current length becomes that array’s fixed length.Array to vector. The array’s fixed length produces the vector value. The receiving vector takes that length and may still grow afterwards via
push/append.
vector<integer> vec = [1, 2, 3]; // current length 3
integer[3] a = vec; // [1, 2, 3]
integer[5] b = vec; // [1, 2, 3, 0, 0] (padded)
integer[*] c = vec; // length inferred as 3, then fixed
integer[2] d = [7, 8];
var vector<integer> w = d; // [7, 8]; w may still grow
call w.push(9); // [7, 8, 9]
11.5. Array to Array
An array value may be implicitly cast to another array type of the same
rank when every element can be implicitly cast to the destination’s element
type. Each element converts by the implicit-cast rule for its own type (the
scalar table of Scalars for scalar elements, applied
recursively for composite elements). The result obeys the destination array’s
fixed length: a shorter value is padded with the
element type’s zero value and a longer value raises a SizeError
(see Errors). An array is never implicitly or explicitly
cast to a different
rank.
integer[3] v = [1, 2, 3];
real[3] u = v; // [1.0, 2.0, 3.0]
u -> std_output;
Output
[1 2 3]
11.6. Character Array to/from String
A string value can be implicitly cast to a character array
(character[*]) and vice versa (a two-way implicit cast). Because a
string is a language-supplied typealias for vector<character> (see
String), this is simply the array/vector implicit cast of
Array to/from Vector specialized to the character element type;
the conversion of note is between string and character arrays.
string str1 = "Hello"; /* str1 == "Hello" */
character[*] chars = str1; /* chars == ['H', 'e', 'l', 'l', 'o'] */
string str2 = chars || [' ', 'W', 'o', 'r', 'l', 'd']; /* str2 == "Hello World" */
str2 -> std_output;
Output
Hello World