Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Casts

The as operator asserts that a value has the given type:

let values = %{};
values.insert(1);
values.insert(2);

// keys returns List<Any>; the cast recovers the element type so the
// result works with functions that expect a List<Int>.
let keys = values.keys as List<Int>;

A cast checks the value without converting it. 5 as Float is an error because 5 is an Int; use conversion functions like float(5) to change a value’s type.

Checking

When the compiler can already prove the cast from the value’s static type, the cast is free: no runtime check is emitted. Otherwise the value is checked at runtime, and a failed check raises an error at the cast site:

let value: Any = ["a"];
value as List<Int>
// error[vm]: cannot cast List<String> to List<Int>

Checking a container inspects every element (and recurses into nested containers), so a cast costs one pass over the value. Empty containers conform to any element type: [] as List<Int> succeeds.

A map’s default value is checked against the value type too, since a missing-key lookup inserts it. A default function’s results can’t be verified without calling it, so such a map only conforms when the value type is Any.

The analyser rejects casts between types that cannot share a value:

"foo" as Int
// error[resolver]: invalid cast: String can never be Int

Precedence

as binds tighter than binary operators and looser than unary operators and calls, so a + b as Int means a + (b as Int). Method calls bind tighter than the cast: recovering an element type before a method call needs parentheses, as in (values.keys as List<Int>).max().

A < after the cast type is read as a type argument list when the tokens that follow form one, and as a less-than comparison otherwise, so n as Int < 10 compares while xs as List<Int> casts.

Casting to state what a value holds

The analyser only accepts what it can prove. Where it cannot, a cast states the value’s type and checks it at the cast site.

Reassigning a variable widens its type, which can lose the detail a function needs:

let line = "a b c";
line = line.split(" ");    // line is now Sequence<String>, not List<String>
line.remove(0);            // error: no `remove` matches Sequence<String>, Int
assert_eq((line as List<String>).remove(0), "a");

A specialized op= mutates its target in place and keeps its type, so it only accepts a right operand that provably fits. A value of unknown type does not, and a cast gets the elements checked instead of letting them slip into the target unverified:

fn opaque(x) => x;
let values = [1];
values ++= opaque([2, 3]) as List<Int>;
assert_eq(values, [1, 2, 3]);

Limitations

Checking an iterator’s elements would consume it, so a runtime check against an element type other than Any fails. Cast a value into a typed collection before turning it into an iterator.