Expressions and Operators
Expressions are the building blocks of Nitro programs - pieces of code that evaluate to a value. This chapter covers literals, operators, and how to combine them to create meaningful computations.
Literals
Literals are values written directly in your code. Nitro supports several types of literals:
Numbers
// Integers
let positive = 42
let negative = -23
let large = 1_000_000 // underscores for readability
// Floating point
let pi = 3.1416
let explicit_float = 5.0f
let scientific = 1e10 // 10,000,000,000
Strings and Characters
// Strings
let greeting = "Hello, world!"
let multiline = "
This is a
multi-line string
"
let raw_string = r#"This is a raw string with no escape sequences: \n"#
// Characters (Unicode)
let letter: Char = u"A"
let unicode_hex: Char = u"\x41" // 'A' in hex
let unicode_full: Char = u"\u0041" // 'A' in Unicode
// Characters ascii
let ascii_char: Int = a"A" // ASCII character 'A' (65 in decimal)
let ascii_hex: Int = a"\x41" // ASCII character 'A' in hex
Other Literals
// Boolean values
let is_ready = true
let is_finished = false
// Nothing (absence of value)
let empty = nothing
Function Calls
Functions are called using their name followed by arguments in parentheses:
// Built-in functions
println("Hello, world!") // Print with newline
print("No newline") // Print without newline
// Functions with multiple arguments
let result = add(5, 3) // Custom function
let max_val = max(10, 20, 15) // Variable arguments
Note: See the Functions chapter for details on defining your own functions.
Operators
Nitro provides a comprehensive set of operators for different types of operations:
Arithmetic Operators
let a = 10
let b = 3
let sum = a + b // Addition: 13
let diff = a - b // Subtraction: 7
let product = a * b // Multiplication: 30
let quotient = a / b // Division: 3
let remainder = a % b // Reminder: 1
Note: '%' is the remainder operator, not modulo. For modulo behavior you can use
a.modulo(b)that will always return a non-negative result.
Comparison Operators
let x = 5
let y = 10
let equal = x == y // Equality: false
let not_equal = x != y // Inequality: true
let less = x < y // Less than: true
let greater = x > y // Greater than: false
let less_equal = x <= y // Less or equal: true
let greater_equal = x >= y // Greater or equal: false
let ordering = x <=> y // Value comparison: Ordering::Less, Ordering::Equals or Ordering::Greater
Logical Operators
let a = true
let b = false
let and_result = a && b // Logical AND: false
let or_result = a || b // Logical OR: true
let xor_result = a ^^ b // Logical XOR: true
let not_result = !a // Logical NOT: false
// Also valid
let and_result = a and b // Logical AND: false
let or_result = a or b // Logical OR: true
let xor_result = a xor b // Logical XOR: true
let not_result = not a // Logical NOT: false
Bitwise Operators
let x = 12 // Binary: 1100
let y = 10 // Binary: 1010
let and_bits = x & y // Bitwise AND: 8 (1000) also x.bitwise_and(y)
let or_bits = x | y // Bitwise OR: 14 (1110) also x.bitwise_or(y)
let xor_bits = x ^ y // Bitwise XOR: 6 (0110) also x.bitwise_xor(y)
let left_shift = x << 2 // Left shift: 48
let right_shift = x >> 2 // Right shift: 3
let unsigned = x >>> 2 // Unsigned right shift: 3 (same as >> for positive numbers)
// There is no operator for bitwise NOT, but you can use the method:
let not_bits = x.bitwise_not() // Bitwise NOT: -13 (0b11111111111111111111111111110011)
Assignment Operators
let value = 10
value += 5 // value = value + 5 -> 15
value -= 3 // value = value - 3 -> 12
value *= 2 // value = value * 2 -> 24
value /= 4 // value = value / 4 -> 6
value %= 3 // value = value % 3 -> 0
Special Operators
// Type checking and casting
let value = 42
let is_int = value is Int // Type checking: true
let not_string = value !is String // Negative type check: true
let as_int = value as Int // Type casting, throws error if not possible
// Membership testing
let numbers = [1, 2, 3, 4, 5]
let contains = 3 in numbers // Contains: true
let not_contains = 6 !in numbers // Not contains: true
// Null coalescing (default values)
let optional_value: Optional<String> = None()
let result = optional_value ?? "default" // Uses default if None
// Safe navigation operator (avoids null dereference)
let maybe_person: Optional<Person> = None()
let name = maybe_person?.name ?? "Unknown" // Safe access, returns "Unknown" if person is None
// Early return operator, return if None(), unwrap if Some()
let definitly_a_person = maybe_person?
Precedence
Operators have a precedence that determines the order in which they are evaluated.
Operators sorted by precedence, from highest to lowest:
| Prec. | Operator |
|---|---|
| 0 | () |
| 1 | [] . ?. !! ? ?? |
| 2 | ! - + (unary) and as is !is in !in |
| 3 | * / % |
| 4 | + - |
| 5 | ..= ..< |
| 6 | << >> >>> |
| 7 | & |
| 8 | ^ |
| 9 | | |
| 10 | < > <= >= |
| 11 | <=> |
| 12 | == != |
| 13 | ^^ |
| 14 | && |
| 15 | || |
| 16 | = += -= *= /= %= |
- Precedence 0 (highest): Will evaluate first
- Precedence 16 (lowest): Will be the last to evaluate
- Operators in the same precedence level are evaluated left-to-right (left-associative)
- Some operators cannot chain, such as
+=and-=, which require a single left-hand operand
Note: This precedence is subject to change in the future
String Interpolation
Nitro supports powerful string interpolation for embedding values and expressions directly in strings:
Basic Interpolation
let name = "Alice"
let age = 42
// Simple variable interpolation
let greeting = "Hello, $name!"
// Expression interpolation
let message = "You are $age years old"
let summary = "In 10 years, you'll be ${age + 10}"
Complex Expressions
Use ${} for complex expressions:
let numbers = [1, 2, 3, 4, 5]
let info = "The list has ${numbers.len} elements"
// Method calls
let text = "HELLO"
let formatted = "Lowercase: ${text.to_lowercase()}"
// Nested interpolation
let nested = "Result: ${"Value is ${42 * 2}"}"
Escape Sequences
let escaped = "Line one\nLine two\tTabbed"
let quote = "She said \"Hello!\""
let backslash = "Path: C:\\folder\\file.txt"
let dolar = "Price: \$100"
Ranges
Ranges represent a sequence of contiguous values efficiently by storing only the start and end values:
Creating Ranges
// Inclusive range (includes both endpoints)
let inclusive = 1..=10 // 1, 2, 3, ..., 9, 10
// Exclusive range (excludes the end)
let exclusive = 1..<10 // 1, 2, 3, ..., 8, 9
Note: The operators
..=and..<can be implemented for any custom type by implementing magic functions.
Using Ranges
let range = 1..=10
// Check membership
println(5 in range) // true
println(15 in range) // false
println(10 in 1..<10) // false (exclusive end)
// Iterate over ranges (in loops)
for i in 1..=5 {
println(i) // Prints 1, 2, 3, 4, 5
}
The Nothing Type
The nothing literal represents the absence of a value, similar to void or Unit in other languages:
// Functions that don't return a value
fun print_message() {
println("This function returns nothing")
} // Implicitly returns: nothing
// Explicit nothing return
fun do_something(): Nothing {
println("Doing something...")
return nothing
}
Important: Unlike null in other languages, nothing is a distinct type that cannot be mixed with other types.
It is used to indicate that a function or expression intentionally does not produce a meaningful value.
The Never Type
The never type represents a situation that will never occur. It is used to make functions that will never return, for
example, with an infinite loop or a critical error that will crash the program:
// Function that never returns
fun infinite_loop(): Never {
loop {
// Do something forever
}
}
// Function that always crashes
fun print_and_exit(msg: String): Never {
println(msg)
runtime::exit(1)
}