Operators
Operators combine, compare, transform, or assign values. Raven follows familiar .NET arithmetic and comparison conventions and adds language-specific operators for pipelines, ranges, null handling, error propagation, and from-end indexes.
let total = price * quantity + shipping
let last = values[^1]
let page = values[10..20]
let normalized = input |> Trim() |> Normalize()
Precedence
Precedence determines which parts of an expression are grouped together when
parentheses are absent. For example, a + b * c means a + (b * c) because
multiplication binds more tightly than addition.
From lowest to highest precedence:
- Pipeline:
|> - Assignment:
= += -= *= /= %= &= |= ^= <<= >>= >>>= ??= - Null-coalescing:
?? - Logical OR:
or,|| - Logical AND:
and,&& - Bitwise OR:
| - Bitwise XOR and AND:
^ & - Equality and relational:
== != < > <= >= - Type tests:
is as - Range and shifts:
.. ..= << >> - Additive:
+ - - Multiplicative:
* / % - Cast:
(T)expression - Prefix:
+ - ! not ~ await fixed stackalloc typeof - Postfix: call
(), member., element access[], nullable suppression!, and propagation?
Standalone postfix propagation has a special boundary at await and try:
await Foo()? is (await Foo())?, and try Foo()? is (try Foo())?.
The propagation wraps the complete prefix expression. Explicit parentheses
can place it inside the operand, as in await (Foo()?) or try (Foo()?).
Conditional access keeps its usual postfix grouping.
Assignments associate from right to left. Other binary operators associate from left to right unless their feature article states otherwise. Use parentheses when they make the intended grouping clearer to a reader.
From-end indexes
Prefix an integer expression with ^ to create a System.Index that counts
from the end of a sequence:
let last = values[^1]
let secondFromEnd: System.Index = ^2
The operand must be implicitly convertible to int. The result retains its
System.Index type even without a target annotation.
When an array is indexed from the end, Raven computes the position from the array's length. The receiver and index operand are each evaluated exactly once.
The prefix is adjacent: ^1 is valid, while ^ 1 is not.
Ranges
Use ..= for an inclusive range and .. for a half-open range
whose upper bound is excluded:
let inclusive = 3..=5
let range = 3..^5
let halfOpen = 3..10
let head = ..3
let tail = 3..
let all = ..
Either bound may be omitted with ..; ..= requires its upper bound. Bounds may
be prefixed with ^ to count from the end. Each
written bound must be implicitly convertible to int and is evaluated once,
from left to right. The expression retains its System.Range type when no
target type is supplied.
Ordinary boundaries use Index.FromStart; ^ boundaries use
Index.FromEnd. Omitting a boundary produces the corresponding
Range.StartAt, Range.EndAt, or Range.All shape.
A one-dimensional array accepts one range argument and produces a sliced array:
let middle = values[2..5]
Other receivers resolve [] through their indexers, so overloads accepting
Index and Range can coexist.
Inside a collection expression or for source, a numeric range is enumerated
instead of being stored as a Range value. See Collection
expressions and Control-flow expressions and
loops.
Inclusive ends are converted to equivalent exclusive System.Index boundaries
using checked arithmetic. Inclusive ^0 and negative distances are invalid;
..=^1 includes the last element. See range lowering.
Bitwise operators
Raven supports bitwise complement ~, bitwise &, |, and ^, and shifts
<< and >>.
~is defined forintandlong.&,|, and^are defined forint,long,bool, and matching enum operands.<<and>>accept anintorlongleft operand and anintshift count.
Smaller integral operands are promoted to int before applying a numeric
bitwise operator. Convert the result explicitly when a byte-sized value is
required by an API or protocol.
When both operands are bool, &, |, and ^ evaluate both sides without
short-circuiting and return bool. Enum operands must have the same enum type,
and the result keeps that type.
Compound assignments &=, |=, and ^= apply the corresponding operator
after evaluating the assignment target once.
Target-typed leading-dot syntax is convenient for enum flags:
let flags: BindingFlags = .Public | .Instance | .Static
let grouped: BindingFlags = .Public | (.Instance | .Static)
func withBinding(flags: BindingFlags) {
// ...
}
withBinding(.Public | .Instance)
Ambiguous-looking forms
(<expression>)is a parenthesized expression. A comma, including a trailing comma, makes it a tuple instead.<begins type arguments only in a type context. Elsewhere it is the less-than operator.- The left side of assignment must be an assignable expression—such as an identifier, member, or element access—or an assignment pattern such as positional deconstruction.
- From-end indexes are adjacent prefix forms: write
^1, not^ 1. - Prefix
+and-are also adjacent: write+3and-2, without whitespace between the operator and operand.
Custom operator declarations and overload selection are described under Parameters, overloading, and operators.
Numeric signedness
Primitive division and remainder use the promoted operand type's signedness.
Right shift extends the sign bit for signed integers and inserts zero bits for
unsigned integers. For example, dividing the largest uint by two yields
2147483647, and shifting it right by one yields the same value. Raven emits the
corresponding signed or unsigned CLI instructions; this is ordinary CLR behavior,
not a neoCLR-specific lowering rule. See CLI arithmetic and shift rules.
Unsigned right shift and shift assignment
>>> shifts in zero bits, including for signed operands; >> retains the sign
for signed operands. Both preserve the promoted left operand type. Shift counts
convert to int and are masked to five bits for 32-bit values or six for 64-bit
values. <<=, >>= and >>>= evaluate the target once and convert predefined
shift results back to its storage type. %= uses the remainder operator.
User-defined >>> uses the standard op_UnsignedRightShift metadata name.