Skip to content

Expressions

Chuks uses an expression-based design, meaning that many constructs typically treated as statements in other languages are actually expressions that produce values. This is similar to languages like Rust and Kotlin.

In most C-family languages, an if block is a statement — it performs an action but doesn’t return a value. In Chuks, if, while, for, for-of, and for-in are all expressions — they evaluate to a value and can be used anywhere a value is expected.

// if as an expression — the result is assigned to a variable
var status = if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else {
"C"
}
println(status) // "A", "B", or "C"

When used as a standalone line (without assigning the result), these expressions are automatically wrapped in an ExpressionStatement — so you can still use them exactly like traditional statements.

// if used as a statement (result is discarded)
if (x > 0) {
println("positive")
}

Chuks distinguishes between expressions (which produce values) and statements (which perform actions).

ExpressionExample
Literals42, "hello", true, [1, 2, 3]
Identifiersx, myVar
Binary operationsa + b, x > 0
Unary operations-x, !done
Function callsgreet("Chuks")
Method callsarr.push(5)
Property accessarr.length, user.name
Index accessarr[0], map["key"]
Slice expressionarr[1:3], str[:5]
Ternarycond ? a : b
Assignmentx = 5
If / Elseif (cond) { a } else { b }
While loopwhile (cond) { ... }
For loopfor (init; cond; update) { ... }
For-of loopfor (item of collection) { ... }
For-in loopfor (key in map) { ... }
Lambda(x: int): int => x * 2
Spawnspawn fetchData()
Awaitawait task
StatementExample
Variable declarationvar x: int = 5
Constant declarationconst pi = 3.14
Function declarationfunction add(a: int, b: int): int { ... }
Class declarationclass Dog extends Animal { ... }
Returnreturn value
Importimport "math"
Interface declarationinterface Printable { ... }
Enum declarationenum Color { Red, Green, Blue }
Expression statementprintln("hello") (an expression used as a statement)
FeatureJava / C#JavaScriptRustKotlinChuks
if as expressionNoNo¹YesYesYes
while as expressionNoNoYes (returns ())NoYes
for as expressionNoNoNo²NoYes
Ternary operatora ? b : ca ? b : cN/A (use if)N/A (use if)Yes (a ? b : c)
Slice expressionsN/Aa.slice()&a[1..3]N/AYes (a[1:3])

¹ JavaScript has the ternary operator ? : but if itself is a statement. ² Rust has for loops but they return (), not a meaningful value.

Chuks supports both the traditional ternary operator (? :) and if-expressions for inline conditionals:

// Ternary operator
var label = count == 1 ? "item" : "items"
// Equivalent if-expression
var label2 = if (count == 1) { "item" } else { "items" }

Ternary expressions are right-associative and can be nested:

var grade = score >= 90 ? "A" : score >= 80 ? "B" : "C"

Chuks provides a standard set of operators that evaluate to values.

Arithmetic operators evaluate to the numeric result of the operation.

  • + (Addition), - (Subtraction), * (Multiplication), / (Division), % (Modulo)
var sum = 10 + 5;
var rem = 10 % 3;

Comparison operators evaluate to a bool.

  • == (Equal), != (Not equal)
  • > (Greater than), < (Less than)
  • >= (Greater than or equal), <= (Less than or equal)
var isEqual = (a == b);
var isValid = (age >= 18);

Logical operators work with bool values and support short-circuit evaluation — the right operand is only evaluated when necessary.

Returns true only if both operands are true. If the left operand is false, the right operand is not evaluated.

var canEnter = (hasTicket && isAdult);
var safe = (ptr != null && ptr.value > 0); // short-circuits if ptr is null

Returns true if either operand is true. If the left operand is true, the right operand is not evaluated.

var allowed = (isAdmin || hasPermission);
var fallback = (config != null || useDefaults());

Negates a boolean value.

var invert = !canEnter;
var isEmpty = !list.length();

The ?? operator returns the left operand if it is not null; otherwise it returns the right operand. Unlike ||, it only checks for null — not for false, 0, or empty strings.

var name = inputName ?? "Anonymous"; // "Anonymous" if inputName is null
var port = configPort ?? 8080; // 8080 if configPort is null

Use ?? when you want to provide a default value for potentially null variables:

function greet(name: string?) {
var displayName = name ?? "Guest";
println("Hello, " + displayName);
}
greet("Alice"); // Hello, Alice
greet(null); // Hello, Guest

These operators mutate the variable exactly by 1.

  • ++ (Increment by 1), -- (Decrement by 1)
var i = 0;
i++; // i is now 1

Compound assignment operators combine an arithmetic operation with assignment. The expression x op= y is equivalent to x = x op y.

OperatorNameEquivalentExample
+=Add and assignx = x + ycount += 1
-=Subtract and assignx = x - yhealth -= damage
*=Multiply and assignx = x * yprice *= quantity
/=Divide and assignx = x / ytotal /= numItems
%=Modulo and assignx = x % yindex %= arrayLength
var count: int = 10;
count += 5; // 15 — equivalent to count = count + 5
count -= 3; // 12
count *= 2; // 24
count /= 4; // 6
count %= 4; // 2

Compound assignments work with all numeric types (int, float) and += also works for string concatenation:

var greeting: string = "Hello";
greeting += ", world!"; // "Hello, world!"
var total: float = 100.0;
total *= 0.85; // Apply 15% discount → 85.0

They are commonly used in loops:

var sum: int = 0;
for (var i: int = 0; i < 100; i += 10) {
sum += i;
}
println(sum); // 450

Bitwise operators work directly on the binary representation of integer values.

OperatorNameDescriptionExample
&ANDSets each bit to 1 if both bits are 10xFF & 0x0F15
|ORSets each bit to 1 if either bit is 10xF0 | 0x0F255
^XORSets each bit to 1 if only one bit is 10xFF ^ 0x0F240
~NOTInverts all bits~0-1
<<Left shiftShifts bits left by n positions1 << 8256
>>Right shiftShifts bits right by n positions256 >> 81
// Bitwise AND — mask the lower nibble
println(0xFF & 0x0F); // 15
// Bitwise OR — combine flags
println(0xF0 | 0x0F); // 255
// Bitwise XOR — toggle bits
println(0xFF ^ 0x0F); // 240
// Bitwise NOT — invert all bits
println(~0); // -1
println(~255); // -256
// Shift operators
println(1 << 4); // 16 (multiply by 2⁴)
println(256 >> 4); // 16 (divide by 2⁴)
println(0xFF << 8); // 65280

Bitwise operators work with variables and can be combined:

var flags: int = 0xAB;
var mask: int = 0x0F;
println(flags & mask); // 11 (lower nibble)
println(flags | mask); // 175
println(flags ^ mask); // 164
println(flags >> 4); // 10 (upper nibble)
println((0xFF & 0x0F) | 0xF0); // 255 (combined operations)
println((1 << 8) - 1); // 255 (bitmask for 8 bits)

The last expression in the matching branch is the value of the entire if expression:

var max = if (a > b) { a } else { b }

Both branches should evaluate to the same type when used as a value.

A while loop is also an expression. In practice, its value is the result of the last iteration (or null if the loop never executes):

var i = 0
while (i < 10) {
i++;
}

Similarly, a for loop is an expression:

var sum = 0
for (var i = 0; i < 5; i++) {
sum += i;
}

Iterates over elements of a collection:

const colors = ["red", "green", "blue"]
for (color of colors) {
println(color);
}

Iterates over keys of a map:

const scores: map[string]int = {"math": 90, "science": 85}
for (subject in scores) {
println(subject + ": " + scores[subject]);
}

The expression-based design offers several advantages:

  1. Fewer language constructsif/else works as an expression, and the ternary operator (? :) offers a concise alternative.
  2. More concise code — Assign values directly from control flow without pre-declaring mutable variables.
  3. Consistency — Everything that evaluates logically has a value, making the language more uniform.
  4. Composability — Expressions can be nested and combined freely.
// Without expression-based design (imperative style):
var result: string;
if (x > 0) {
result = "positive";
} else {
result = "negative";
}
// With expression-based design (concise):
var result: string = if (x > 0) { "positive" } else { "negative" };