Tuples

Tuples are a fixed-order, fixed-size collection of values which do not all have to be of the same type.

Compile-Time Validated Indexing

You can see in the example below, tuples interact w/ type validation in an interesting way worth making note of. When you index into a tuple, you should generally prefer to use a literal int constant. When you do, Claro can statically determine the type of the value you're accessing at compile time, which allows safer and more efficient code.

For example, Claro is able to statically find the bugs in the below program:

Fig 1:


var myPair: tuple<int, string> = (1, "one");

# Claro will interpret literal int subscripts at compile-time for type validation.
var myInt: int = myPair[1];     # <-- Wrong index.
print(myInt);
var myStr: string = myPair[3];  # <-- Out-of-bounds index.
print(myStr);

Compilation Errors:

tuple_type_EX1_example.claro:4: Invalid type:
	Found:
		string
	Expected:
		int
var myInt: int = myPair[1];     # <-- Wrong index.
                 ^^^^^^^^^
tuple_type_EX1_example.claro:6: Tuple Subscript Literal Out of Bounds:
	For subscript on tuple of type: tuple<int, string>
	Found:
		3
	Expected:
		index in range [0, 2)
var myStr: string = myPair[3];  # <-- Out-of-bounds index.
                    ^^^^^^^^^
2 Errors

Making it easy to apply the fix:

Fig 2:


var myPair: tuple<int, string> = (1, "one");

# Claro will interpret literal int subscripts at compile-time for type validation.
var myInt: int = myPair[0];     # <-- Correct index.
print(myInt);
var myStr: string = myPair[1];  # <-- Correct index.
print(myStr);

Output:

1
one

Runtime Validated Indexing

If your index value is hidden behind some indirection, Claro can't know the type at compile-time and will require a runtime type cast (which is slow & opens the door to runtime Panics if the actual type doesn't match the asserted type).

Claro will warn you at compile-time when this is necessary:

Fig 3:


var myPair: tuple<int, string> = (1, "one");

# Claro requires a type cast for non-literal index.
var index: int = 0;
var myInt = myPair[index];
print(myInt);

Compilation Errors:

tuple_type_EX3_example.claro:5: The type of this expression is UNDECIDED at compile-time! You must explicitly cast the Expr to the expected type to assert this type at compile-time.
var myInt = myPair[index];
            ^^^^^^^^^^^^^
tuple_type_EX3_example.claro:6: No variable <myInt> within the current scope!
print(myInt);
      ^^^^^
2 Errors

Fix the error by explicitly asserting the expected type by casting:

Fig 4:


var myPair: tuple<int, string> = (1, "one");

# Claro requires a type cast for non-literal index.
var index: int = 0;
var myInt = cast(int, myPair[index]); # OK, opting into runtime type validation.
print(myInt);

Output:

1

Mutable Tuples

Unlike some other languages with tuple support, Claro imposes no arbitrary restriction that all tuples must necessarily be immutable. Just like any other builtin collection type, a Claro tuple may be declared mutable using the mut keyword when declaring a variable or initializing the value. You may then update element values at will as long as the initial type declaration for each element is honored.

Claro won't allow you to mutate a tuple that isn't explicitly declared mutable:

Fig 5:


var myPair = (1, "one"); # <-- This is an **immutable** tuple.
print(myPair);

myPair[0] = 99;  # <-- Illegal attempted mutation happens here.
print(myPair);

Compilation Errors:

tuple_type_EX5_example.claro:4: Illegal Mutation of Immutable Value: Mutation of immutable values is forbidden!
	Found the immutable type:
		tuple<int, string>
	In order to mutate this value, the value's type would need to be updated to:
		mut tuple<int, string>
myPair[0] = 99;  # <-- Illegal attempted mutation happens here.
^^^^^^
1 Error

The mutation is legal when the tuple is explicitly annotated mut:

Fig 6:


var myPair = mut (1, "one");  # <-- This is now a **mutable** tuple.
print(myPair);

myPair[0] = 99;  # <-- Mutation happens here.
print(myPair);

Output:

mut (1, one)
mut (99, one)