Comments start with # and run to the end of the line. There are no block comments.
# This is a comment
let x = 42 # inline comment
Use let for immutable bindings and mut for mutable ones. Type annotations are optional when the type can be inferred.
let x = 42
let name: Str = "Alice"
mut counter = 0
counter = counter + 1
counter += 1 # compound assignment (also -=, *=, /=)
Variables declared at module level are accessible from all functions in the module:
let MAX_SIZE = 100
def check_size(n: Int) -> Bool {
return n <= MAX_SIZE
}
Every value has a type known at compile time. See Types for the full reference.
| Type | Example |
|---|---|
Int | 42 |
Float | 3.14 |
Bool | True, False |
Str | "hello" |
Never | return type for functions that never return (e.g. exit()) |
Append ? to any type to make it optional. Optional values are either a value of that type or None.
let result: Int? = find("Alice")
if result is not None {
print(result + 1) # narrowed to Int inside this block
}
if result is None {
print("not found")
}
let value = result ?: 0 # elvis: use 0 if None
let upper = name?.upper() # safe call: returns None if name is None
let forced = result!! # non-null assertion (throws if None)
is None and is not None are the idiomatic way to check for None. The equality forms == None and != None also work and trigger the same flow narrowing.
Fixed-size groups of values with potentially different types:
let point = (1, 2)
let (x, y) = point # destructuring
let (a, b, c?) = (1, 2) # optional element: c is None
Tuples support 2 to 8 elements. For larger groupings, use a data type.
let transform: (Int) -> Int = lambda x: Int: x * 2
Classes, data types, and functions can take type parameters:
data Pair[A, B] {
first: A
second: B
}
def identity[T](x: T) -> T {
return x
}
Type arguments are inferred from usage. See Types for more.
Strings use double quotes. F-strings embed expressions with f"...":
let name = "world"
print(f"Hello, {name}!")
print(f"2 + 2 = {2 + 2}")
Indexing and slicing:
let s = "Hello, World!"
print(s[0]) # "H"
print(s[0:5]) # "Hello"
print(s[7:]) # "World!"
Common methods: upper(), lower(), strip(), split(sep), split_once(sep), join(list), replace(old, new), startswith(prefix), endswith(suffix), is_empty(), non_empty(). See Collections and strings for the full list.
Prefix a string with r to disable escape processing. Useful for regex patterns and file paths:
let pattern = r"\d+\.\d+"
let path = r"C:\Users\name\docs"
Both r"..." and r'...' are supported.
Use """...""" for multiline strings. Triple-quoted strings support escape sequences and automatically strip common leading indentation:
let sql = """
SELECT name, age
FROM users
WHERE active = true
"""
# Result: "SELECT name, age\nFROM users\nWHERE active = true"
Auto-dedent rules: the leading newline after """, the trailing newline before closing """, and the common leading whitespace of all non-empty lines are removed. Escape sequences (\n, \t, \\, etc.) work inside triple-quoted strings.