operators
The arithmetic, concatenation, comparison, equality, logical, bitwise, and shift operators. Every binary operator requires its two operands to have the same type.
const sum: int32 = (42: int32) + 8;
sum // => 50
"hello" ++ " world" // => "hello world"
(40: int32) < 42 // => truePrecedence
Binary operators are left-associative and the unary prefixes are right-associative. The levels, from the one that binds loosest to the one that binds tightest:
||and|^&&and&==!=<<=>>=<<>>+++-*/%- unary
!~- - postfix
.[]()
Some of those placements differ from a C-style layout. ^ sits on its own level between AND and OR.
Equality, comparison, and the shifts all share one level. And ++ binds tighter than + and
-, so a ++ b + c groups as a ++ (b + c). Ascription binds below every operator, looser than
||.
Arithmetic
+ - * / % are defined over every numeric type, and the result is the operand type.
- A non-numeric operand is rejected;
++is the operator for strings and arrays. - Unary
-is defined on signed numerics only, so it does not apply to auintN. - Division truncates toward zero, and
%takes the sign of its left operand. - Division and modulo by zero are runtime failures, so
tryturns one intonone.
(-7: int32) / 2 // => -3
(-7: int32) % 2 // => -1
try((7: int32) / 0) // => noneConcatenation
++ joins two strings, or two arrays of one element type, and yields that same type. Mixing a string
with a non-string, or arrays of different element types, is rejected.
Comparison
< <= > >= are defined over the numeric types and yield bit.
- Comparison is total: it always yields a
bitand never fails. - A
NaNoperand is unordered, so all of them yieldfalsewhen either side isNaN. - One consequence is worth stating rather than tripping over:
!(a < b)is nota >= bwhen either operand may beNaN, because both are thenfalse.
sqrt((-1.0: float64)) < (1.0: float64) // => false
sqrt((-1.0: float64)) >= (1.0: float64) // => falseEquality
== and != are defined over any one type shared by both operands, and yield bit. x != y is
!(x == y).
- Equality is structural: primitives by value, tuples and arrays element-wise, structs field-wise with field order irrelevant, variants by constructor and payload.
- Arrays must match in length as well as contents.
NaN == NaNisfalse, following IEEE-754.
const head: uint8[] = [1, 2];
const longer: uint8[] = [1, 2, 3];
(true, "air") == (true, "air") // => true
head == longer // => falseLogical and bitwise
&& and || are dual. On bit they are short-circuiting logical AND and OR, so the right operand
goes unevaluated once the left decides the result; on intN/uintN they are bitwise AND and OR, and
both operands are always evaluated.
&and|are synonyms for&&and||, at the same precedence and with the same meaning.^is XOR — bitwise on integers, logical onbit.- Unary
!and its synonym~are dual the same way: logical negation on abit, bitwise complement on an integer, yielding the operand's own type. - None of these are defined on the float types.
true && false // => false
(5: uint8) || 3 // => 7
(5: uint8) ^ 3 // => 6
!(5: uint32) // => 4294967290Shifts
<< and >> are defined over intN/uintN, and the result is the left operand's type. >> is
lexed as two adjacent > tokens, which is why Optional<Optional<T>> closes correctly.
- Both operands are the same integer type.
- The shift amount is in
[0, N)for anN-bit operand. A literal amount at or above the width is rejected at check time; a runtime amount out of range yields zero. <<zero-fills the low bits.>>zero-fills on auintNand sign-extends on anintN.
(0b01000000: uint8) << 1 // => 128
(0b10000000: uint8) >> 1 // => 64
(-8: int64) >> 2 // => -2Overflow
Integer arithmetic is fixed-width and wraps: a uintN result reduces modulo 2^N, and an intN
result wraps two's-complement. Floats follow IEEE-754, where overflow yields inf and an invalid
operation yields NaN.
- Wrapping happens at the width inference pinned, so the same literals wrap differently under different annotations.
- A
value_castis not subject to the wrapping rule. It widens losslessly or reports absence. - Evaluation never panics on a numeric operation.
const limit: uint8 = 250;
const ceiling: int8 = 127;
limit + 10 // => 4
ceiling + 1 // => -128Operand types
The type each operator group accepts, and what it yields:
+ - * / %— matching numerics, yielding the operand type.++— two strings or two arrays of one element type, yielding that type.< <= > >=— matching numerics, yieldingbit.== !=— any one type, yieldingbit.&& || & | ^— bothbitor matching integers, yielding the operand type.<< >>— matching integers, yielding the left operand's type.- unary
! ~— abitor an integer, yielding the operand type. - unary
-— a signed numeric, yielding the operand type.
Errors
Operands the operator does not accept
Both operands must have the same type, and each operator accepts only some types — there is no promotion between widths and no coercion between categories.
const mixed: int32 = (42: int32) + (43: int16); // error: invalid-operand-types
const crossed: bit = (42: int32) == (42.0: float64); // error: invalid-operand-types
const negated: uint8 = -(5: uint8); // error: invalid-operand-types
const flipped: float32 = !(3.14: float32); // error: invalid-operand-types
const shifted: uint8 = (1: uint8) << 8; // error: invalid-operand-typesRelated
- primitives — the types these operators are defined over.
- value-cast — the conversion between two numeric types.
- literals — how an operand's width gets pinned.