Expressions
Primary Expressions
Section titled “Primary Expressions”Array Literals
Section titled “Array Literals”do main() {{1, 2, 3}{"a", "b", "c"}{} // Empty array (not to be confused with {:} which is an empty map)}All elements in an array literal must be the same type. Mixed-type literals are rejected at compile time:
do main() { // {1, "two", 3} // error: mixed types in array literal mut numbers [i64] = {1, 2, 3} println(numbers)}Map Literals
Section titled “Map Literals”See Maps for map literal syntax, including the {:} empty map literal.
Struct Literals
Section titled “Struct Literals”const Point struct { x i64 y i64}
const Person struct { name string age i64 active bool}
do main() { mut a Point = Point{x: 10, y: 20} mut b Person = Person{name: "Alice", age: 30, active: true} mut c Point = Point{} // Zero-initialized println(a) println(b) println(c)}Operators
Section titled “Operators”Arithmetic Operators
Section titled “Arithmetic Operators”| Operator | Description | Operand Types | Result Type |
|---|---|---|---|
+ |
Addition | i64, i64 |
i64 |
+ |
Addition | f64, f64 |
f64 |
+ |
Concatenation | string, string |
string |
- |
Subtraction | i64, i64 |
i64 |
- |
Subtraction | f64, f64 |
f64 |
* |
Multiplication | i64, i64 |
i64 |
* |
Multiplication | f64, f64 |
f64 |
/ |
Division | i64, i64 |
i64 (truncated) |
/ |
Division | f64, f64 |
f64 |
% |
Modulo | i64, i64 |
i64 |
Division by zero produces a runtime error.
+ also concatenates two string operands, producing a new string. Both
operands must be strings; mixing a string with any other type is an error
(use string interpolation or fmt.format() to build strings from other types).
+= appends to a mutable string.
Comparison Operators
Section titled “Comparison Operators”| Operator | Description |
|---|---|
== |
Equal |
!= |
Not equal |
< |
Less than |
> |
Greater than |
<= |
Less than or equal |
>= |
Greater than or equal |
Comparison operators return bool.
Comparison operators only work on primitive types (numeric kinds, bool, char, string for equality, enums) and pointer equality (== / != against nil or another pointer of the same pointee type). They are not defined on aggregate types:
- Arrays — use
arrays.is_equal(a, b)for equality. - Maps — use
maps.is_equal(a, b)for equality; ordering is not defined on maps. - Structs — compare individual fields (
a.x == b.x). - Pointer arithmetic and ordering are not supported; equality (
==/!=) on pointers is allowed.
Logical Operators
Section titled “Logical Operators”| Operator | Description |
|---|---|
&& |
Logical AND (short-circuit) |
|| |
Logical OR (short-circuit) |
! |
Logical NOT |
Logical AND and OR use short-circuit evaluation: the right operand is not evaluated if the result can be determined from the left operand alone.
Membership Operators
Section titled “Membership Operators”| Operator | Description |
|---|---|
in |
Membership test |
not_in |
Non-membership test |
!in |
Non-membership test (shorthand for not_in) |
import @arrays
do main() { mut numbers [i64] = {1, 2, 3} mut lookup map[string:i64] = {"a": 1} mut x i64 = 5
if 3 in numbers { println("3 in numbers") } if "key" not_in lookup { println("key not in lookup") } if x in range(0, 10) { println("x in range") } // if 10 !in range(0, 10) { } // '!in' is shorthand for not_in (don't mix them in one file)}Assignment Operators
Section titled “Assignment Operators”| Operator | Description |
|---|---|
= |
Assignment |
+= |
Addition assignment (string append when the target is a string) |
-= |
Subtraction assignment |
*= |
Multiplication assignment |
/= |
Division assignment |
%= |
Modulo assignment |
Increment and Decrement
Section titled “Increment and Decrement”| Operator | Description |
|---|---|
++ |
Post-increment |
-- |
Post-decrement |
do main() {mut x i64 = 5x++ // x is now 6x-- // x is now 5}Bitwise Operators
Section titled “Bitwise Operators”Grayscale uses keyword operators for bitwise operations. Symbol alternatives (&, ^, |) are unavailable because ^ is the pointer type and dereference sigil and & is used for mutable parameters in function signatures.
| Operator | Syntax | Description | Operand Types |
|---|---|---|---|
bit_and |
a bit_and b |
Bitwise AND | integer types and char |
bit_or |
a bit_or b |
Bitwise OR | integer types and char |
bit_xor |
a bit_xor b |
Bitwise XOR | integer types and char |
bit_not |
bit_not a |
Bitwise NOT (complement) | integer types and char |
bit_shift_left |
a bit_shift_left n |
Left shift by n bits |
integer types and char |
bit_shift_right |
a bit_shift_right n |
Right shift by n bits |
integer types and char |
bit_not is a prefix operator. All others are infix operators. Results have the same type as the operands.
// Basic operations
do main() {mut a i64 = 0b1010mut b i64 = 0b1100
println(a bit_and b) // 8 (0b1000)println(a bit_or b) // 14 (0b1110)println(a bit_xor b) // 6 (0b0110)println(bit_not a) // -11 (bitwise complement)println(1 bit_shift_left 3) // 8println(16 bit_shift_right 1) // 8}A common use is flag manipulation with named constants:
do main() {const READ i64 = 0b001const WRITE i64 = 0b010const EXEC i64 = 0b100
mut perms i64 = READ bit_or WRITE // set READ and WRITE flags
if perms bit_and READ == READ { println("readable")}if perms bit_and EXEC == 0 { println("not executable")}
perms = perms bit_xor WRITE // clear WRITE flag}Operator Precedence
Section titled “Operator Precedence”From highest to lowest precedence:
- Parentheses:
() - Prefix/Unary:
!,-(negation),bit_not - Multiplicative:
*,/,% - Additive:
+,- - Shift:
bit_shift_left,bit_shift_right - Membership:
in,not_in - Comparison:
<,>,<=,>= - Bitwise:
bit_and,bit_or,bit_xor - Equality:
==,!= - Logical AND:
&& - Logical OR:
||
Index Expressions
Section titled “Index Expressions”do main() {mut arr [i64] = {10, 20, 30}mut val i64 = arr[1] // 20arr[0] = 100 // Modification
mut str string = "hello"mut c char = str[0] // 'h'
mut m map[string:i64] = {"a": 1}mut v i64 = m["a"] // 1}Member Expressions
Section titled “Member Expressions”const Direction enum { NORTH EAST SOUTH WEST}
const Point struct { x i64 y i64}
do main() { mut p Point = Point{x: 10, y: 20} mut x i64 = p.x // 10 p.y = 30 // Modification
mut status i64 = Direction.NORTH // Enum access println(x) println(status)}Call Expressions
Section titled “Call Expressions”do add(a i64, b i64) -> i64 { return a + b }do greet(name string) -> string { return "Hello, ${name}" }
do main() { mut sum i64 = add(1, 2) mut greeting string = greet("World") println("Hello!") println(sum) println(greeting)}Range Expressions
Section titled “Range Expressions”do main() {range(0, 10) // 0, 1, 2, ..., 9 (increment)range(0, 10, 2) // 0, 2, 4, 6, 8 (increment)range(10, 0, -1) // 10, 9, 8, ..., 1 (decrement)range(10, 0, -2) // 10, 8, 6, 4, 2 (decrement)}Ranges are inclusive of the start value and exclusive of the end value.
A for loop over a range gives its variable the type i64. When any bound is a wide
integer (i128, u128, i256, u256), the range runs in the widest of them, every
bound and the step are checked as that type (see Sized Types), and
the loop variable has that type. A u64 bound of an i64 range, or a u128 bound of an
i128 range, crosses signedness and is E3019.
Step validation rules:
- Positive step (or omitted) expects start ≤ end; negative step expects start ≥ end.
A range that violates this (its step points away from end, e.g.
range(0, 10, -1)) yields no elements. - When such a range is written entirely with integer literals and drives a
forloop (for _ in range(0, 10, -1)), the compiler rejects it up front withE9005. If any operand is a variable, there is no diagnostic — the loop body just never runs. start == endis always a valid empty range.- Zero step always panics at runtime with
P0090, for literal and variable operands alike.