Type Tests
value is Type answers a question about the type of value and produces a bool. What the question is depends on the operand: on a sum it asks which member is active, on an optional whether a value is present, and on anything else it compares exact types at compile time.
type-test = expr "is" postfix-type
struct Plus {}
struct Minus {}
struct Number {
value: int32;
}
type Token = Plus | Minus | Number;
func IsOperator(token: Token) -> bool {
return token is (Plus | Minus);
}
func Count(tokens: Token[..]) -> uint {
var numbers: uint = 0;
for token in tokens {
if token is Number {
numbers += 1;
}
}
return numbers;
}
What is tested
The tested type is resolved first; together with the operand's type it selects the meaning:
| Operand | value is T is true when |
|---|---|
a sum A | B | C | the active member is T, or one of the members of a tested subset (A | B) |
an optional P? | the value is present and its payload is T — or, for a sum payload, a member or subset of it |
a fallible T ! E | — rejected; match .Success(…) or .Failure(…) instead |
| any other type | the operand's type is exactly T, after aliases are resolved — decided at compile time |
let reading: int32? = 7;
let missing: int32? = none;
let a = reading is int32; // true: present
let b = missing is int32; // false: absent
let nested: int32?? = .Some(none);
let c = nested is int32?; // true: one level is present
let maybe: (int32 | bool)? = true;
let d = maybe is bool; // true: present, and the payload's member is bool
let count: int32 = 7;
let e = count is int32; // true, known before the program runs
A test inspects one optional level only. On an int32??, the payload is an int32?, so nested is int32 is rejected:
error: type 'int32' is neither the payload of optional 'int32??' nor a member of it
help: 'is' tests one presence level; match '.Some(...)' to inspect deeper levels
A test that cannot be true is an error
A test whose answer is known to be false is a mistake, so the compiler rejects it rather than folding it to a constant:
error: type 'bool8' is not a member or subset of sum 'Minus | Number | Plus'
error: 'is int64' can never be true for a value of type 'int32'
help: an 'is' test on a value that is not a sum or an optional compares its exact type
On a value that is neither a sum nor an optional, is compares types by name and identity, never by layout: a struct with the same fields under another name is a different type. An is test on such a value can only be true, which makes it useful mostly in generic code.
A fallible has no type to test — its question is which channel, which is a match:
error: 'is' cannot test the channel of fallible 'int32 ! E'
help: match '.Success(...)' or '.Failure(...)' instead
is never narrows
A test does not change the static type of its operand. After token is Number succeeds, token is still the whole sum, and its member's fields are still out of reach:
if token is Number {
PrintLine("{}", token.value); // error
}
error: type 'Minus | Number | Plus' has no field 'value'
To use the member, take it out with a pattern:
match token {
n: Number => PrintLine("{}", n.value),
else => {}
}
is also never consumes, moves or binds anything. A borrowed operand is tested through the borrow.
Grouping
The right operand is a postfix type, so a sum or fallible type must be grouped:
error: a sum type after 'is' must be grouped
help: write 'value is (Plus | Minus)'
is shares its precedence with as: it binds tighter than every binary operator, so count is int32 && count > 0 needs no parentheses. Group it when it is the operand of a prefix operator: !(missing is int32).
Generic code
A test whose operand or tested type mentions a type parameter is checked again for every instantiation. value is T on a T | U stays valid when T and U are the same type and the sum collapses — it is then a plain type comparison, and true:
func IsLeft<T, U>(value: T | U) -> bool {
return value is T;
}
IsLeft<int32, bool>(true) is false, and IsLeft<int32, int32>(5) is true.
Interfaces
is does not test whether a value implements an interface. That would be a run-time question about the value's type, and it is reported as unavailable rather than answered wrongly:
error: type test 'is Shape' is unavailable: interface checks are not implemented
Membership is not interface implementation either: knowing that every member of a sum implements an interface, or that a test succeeded, makes no method available on the sum.
See also
- Sum patterns — taking the member out
- Optionals — presence and absence
- Casts —
as, which shares the precedence ofis - Learn: Is