Language Guide
A complete reference for the Zamin programming language. This guide covers every core feature — from variables and types to structs, pattern matching, and modules. Each section includes explanations and runnable code examples.
Variables and Types
Zamin uses let for mutable variables and const for constants. You never write explicit type annotations — the compiler infers types from the assigned value.
let x = 42;
let name = "Zamin";
let pi = 3.14159;
let active = true;
const MAX_SIZE = 1024;
const GREETING = "hello";
Core Types
Zamin provides these built-in types:
int— signed 64-bit integer (i64)uint— unsigned 64-bit integer (u64)float— 64-bit floating-point (f64)bool—trueorfalsestring— UTF-8 stringnil— absence of valuelist— ordered, growable collectiondict— key-value mappingset— unordered collection of unique valuestuple— fixed-size, immutable collection
Type Conversion
Use built-in conversion functions to change between types:
let n = int("42"); // 42
let f = float("3.14"); // 3.14
let s = string(100); // "100"
let b = bool(1); // true
let b2 = bool(0); // false
Constants must be assigned a value at declaration time and cannot be reassigned. Use them for configuration values and mathematical constants like const PI = 3.14159;
Strings
Strings in Zamin are UTF-8 encoded. Both double quotes and single quotes create identical strings.
let s1 = "hello";
let s2 = 'hello';
// s1 and s2 are equivalent
F-Strings
Prefix a string with f to embed expressions inside {}:
let name = "Zamin";
let version = 2;
println(f"Welcome to {name} v{version}!");
// Output: Welcome to Zamin v2!
let x = 10;
println(f"Result: {x * 2 + 1}");
// Output: Result: 21
Concatenation and Repetition
let greeting = "Hello" + ", " + "world!";
println(greeting);
// Output: Hello, world!
let pattern = "ab" * 3;
println(pattern);
// Output: ababab
Use the string module for advanced operations like splitting, joining, trimming, and case conversion. See Standard Library for details.
Numbers
Integer Literals
let decimal = 42;
let negative = -7;
let hex = 0xff;
let binary = 0b1010;
let octal = 0o77;
Unsigned Integers
let count = 42u;
let size = 1024u;
Floating-Point Literals
let pi = 3.14159;
let negative = -0.5;
let huge = 1e10;
Arithmetic Operators
| Operator | Description | Example |
|---|---|---|
+ | Addition | 3 + 2 → 5 |
- | Subtraction | 3 - 2 → 1 |
* | Multiplication | 3 * 2 → 6 |
/ | Division | 7 / 2 → 3.5 |
// | Integer division | 7 // 2 → 3 |
% | Modulo | 7 % 2 → 1 |
** | Power | 2 ** 10 → 1024 |
println(10 / 3); // 3.33333...
println(10 // 3); // 3
println(10 % 3); // 1
println(2 ** 8); // 256
Comparison Operators
All standard comparisons work: ==, !=, <, >, <=, >=.
Use the math module for advanced math functions like math.sqrt(), math.abs(), math.floor(), and math.ceil().
Booleans
Zamin has two boolean literals: true and false.
Logical Operators
let a = true;
let b = false;
println(a and b); // false
println(a or b); // true
println(not a); // false
if not b {
println("b is false");
}
Truthiness
In conditional contexts (like if and while), the following values are considered falsy:
falsenil0and0.0""(empty string)[](empty list)
Everything else is truthy, including non-empty strings, non-zero numbers, and non-empty collections.
if "hello" {
println("truthy!");
}
if 0 {
println("never reached");
}
if [] {
println("never reached");
}
Collections
Lists
Lists are ordered, heterogeneous, and growable. Create them with square brackets.
let fruits = ["apple", "banana", "cherry"];
let mixed = [1, "two", true, 3.0];
// Indexing (0-based)
println(fruits[0]); // "apple"
println(fruits[-1]); // "cherry" (last element)
// Slicing
println(fruits[0:2]); // ["apple", "banana"]
// Modification
fruits.push("date");
fruits.pop();
// Length
println(len(fruits)); // 3
// Membership
if "apple" in fruits {
println("Found apple!");
}
Dicts
Dicts map keys to values. Use curly-brace syntax and dot-notation for access.
let user = {
name: "Alice",
age: 30,
active: true,
};
// Access with dot notation
println(user.name); // "Alice"
// Access with bracket notation
println(user["age"]); // 30
// Add / update fields
user.email = "alice@example.com";
// Get with default
let city = user.get("city", "unknown");
// Iterate
for key in user.keys() {
println(f"{key}: {user[key]}");
}
for value in user.values() {
println(value);
}
for key, value in user.items() {
println(f"{key} = {value}");
}
Sets
Sets store unique, unordered values. Use curly-brace syntax with distinct elements.
let colors = {"red", "green", "blue"};
colors.add("yellow");
colors.remove("green");
println(len(colors)); // 3
let a = {1, 2, 3};
let b = {2, 3, 4};
println(a | b); // {1, 2, 3, 4} — union
println(a & b); // {2, 3} — intersection
println(a - b); // {1} — difference
if 2 in a {
println("a contains 2");
}
Tuples
Tuples are fixed-size, immutable collections. Once created, they cannot be changed.
let point = (3, 4);
println(point[0]); // 3
println(point[1]); // 4
// Destructuring
let (x, y) = point;
println(f"x={x}, y={y}");
// Return multiple values from a function
func minmax(list) {
return (min(list), max(list));
}
let (lo, hi) = minmax([3, 1, 4, 1, 5]);
println(f"min={lo}, max={hi}");
List Comprehensions
Create lists with concise expressions that iterate, filter, and transform in a single line.
let doubled = [x * 2 for x in 0..10];
// [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
let evens = [x for x in 0..20 if x % 2 == 0];
// [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
let words = ["hello", "world"];
let upper = [w.to_upper() for w in words];
// ["HELLO", "WORLD"]
let nums = [1, 2, 3, 4, 5];
let filtered = [x * x for x in nums if x > 2];
// [9, 16, 25]
Range Expressions
Ranges generate sequences of integers. They are commonly used in for loops and comprehensions.
// Exclusive end (0 to 9)
for i in 0..10 {
print(f"{i} ");
}
// Output: 0 1 2 3 4 5 6 7 8 9
// Inclusive end (0 to 10)
for i in 0..=10 {
print(f"{i} ");
}
// Output: 0 1 2 3 4 5 6 7 8 9 10
let list = [10..=15];
println(list); // [10, 11, 12, 13, 14, 15]
Use .. when the end value should be excluded (like Python's range()) and ..= when you want the end value included.
Control Flow
If / Elif / Else
let score = 85;
if score >= 90 {
println("A");
} elif score >= 80 {
println("B");
} elif score >= 70 {
println("C");
} else {
println("F");
}
// Output: B
Ternary Expressions
Assign the result of a conditional expression to a variable:
let age = 20;
let status = if age >= 18 { "adult" } else { "minor" };
println(status); // "adult"
let x = 5;
let label = if x > 0 { "positive" } else if x < 0 { "negative" } else { "zero" };
println(label); // "positive"
While Loops
let count = 0;
while count < 5 {
println(count);
count += 1;
}
// Infinite loop with break
let total = 0;
while true {
let input = read_line();
if input == "quit" {
break;
}
total += int(input);
}
For Loops
// Iterate over a list
let items = ["a", "b", "c"];
for item in items {
println(item);
}
// Iterate with index
for i, item in items {
println(f"{i}: {item}");
}
// Iterate over a range
for i in 0..5 {
println(i);
}
// Iterate with inclusive range
for i in 0..=5 {
println(i);
}
// Iterate over a dict
let map = { x: 1, y: 2 };
for key, value in map {
println(f"{key} = {value}");
}
Break and Continue
// break exits the innermost loop
for i in 0..100 {
if i == 5 {
break;
}
println(i);
}
// Output: 0 1 2 3 4
// continue skips to the next iteration
for i in 0..10 {
if i % 2 == 0 {
continue;
}
println(i);
}
// Output: 1 3 5 7 9
Match (Pattern Matching)
The match statement compares a value against multiple patterns and executes the matching branch. Use _ as the wildcard for a default case.
let command = "start";
match command {
case "start" => println("Starting..."),
case "stop" => println("Stopping..."),
case "pause" => println("Pausing..."),
case _ => println("Unknown command"),
}
// Matching numbers
let day = 3;
match day {
case 1 => println("Monday"),
case 2 => println("Tuesday"),
case 3 => println("Wednesday"),
case 4 => println("Thursday"),
case 5 => println("Friday"),
case 6 | 7 => println("Weekend"),
case _ => println("Invalid day"),
}
// Matching with destructuring
let point = (0, 5);
match point {
case (0, 0) => println("Origin"),
case (0, _) => println("On Y-axis"),
case (_, 0) => println("On X-axis"),
case (x, y) => println(f"({x}, {y})"),
}
Match cases use => to separate the pattern from the result. The | operator allows multiple patterns to share the same branch.
Functions
Named Functions
Define functions with the func keyword and a block body:
func add(a, b) {
return a + b;
}
println(add(3, 4)); // 7
Expression Bodies
For short functions, use an expression body instead of a block:
func add(a, b) = a + b;
func square(n) = n ** 2;
func greet(name) = f"Hello, {name}!";
println(square(5)); // 25
println(greet("Zamin")); // "Hello, Zamin!"
Lambda / Closure Expressions
Anonymous functions can be assigned to variables:
let add = func(a, b) {
return a + b;
};
let multiply = func(a, b) = a * b;
println(add(2, 3)); // 5
println(multiply(4, 5)); // 20
Default Arguments
func greet(name = "World") {
println(f"Hello, {name}!");
}
greet(); // "Hello, World!"
greet("Alice"); // "Hello, Alice!"
Variadic Arguments
Prefix a parameter with * to accept any number of arguments:
func sum(*nums) {
let total = 0;
for n in nums {
total += n;
}
return total;
}
println(sum(1, 2, 3, 4)); // 10
Closures
Functions can capture and use variables from their enclosing scope:
func make_counter() {
let count = 0;
return func() {
count += 1;
return count;
};
}
let counter = make_counter();
println(counter()); // 1
println(counter()); // 2
println(counter()); // 3
Higher-Order Functions
Functions can be passed as arguments to other functions:
func apply(f, value) {
return f(value);
}
let double = func(x) = x * 2;
let triple = func(x) = x * 3;
println(apply(double, 5)); // 10
println(apply(triple, 5)); // 15
let nums = [1, 2, 3, 4, 5];
let doubled = [apply(double, x) for x in nums];
println(doubled); // [2, 4, 6, 8, 10]
Zamin supports common functional operations like map, filter, and reduce — either as built-in functions or methods on lists. List comprehensions are often preferred for readability.
Structs
Structs are Zamin's primary mechanism for creating custom types with named fields.
Defining and Instantiating
struct Point {
x,
y,
}
let p = Point(3, 4);
println(p.x); // 3
println(p.y); // 4
Init Method
Define an init function on a struct to run custom logic during construction. The init function receives the struct instance and can set default or computed field values.
struct Rectangle {
width,
height,
}
func Rectangle.init(r) {
r.area = r.width * r.height;
r.perimeter = (r.width + r.height) * 2;
}
let rect = Rectangle(10, 5);
println(rect.area); // 50
println(rect.perimeter); // 30
Methods
Assign closures to struct fields to create methods:
struct Circle {
radius,
}
func Circle.area(self) = 3.14159 * self.radius ** 2;
func Circle.circumference(self) = 2 * 3.14159 * self.radius;
func Circle.describe(self) = f"Circle with radius {self.radius}";
let c = Circle(5);
println(c.area()); // 78.53975
println(c.circumference()); // 31.4159
println(c.describe()); // "Circle with radius 5"
Struct Comparison
Structs of the same type compare by value when all their fields are comparable:
let a = Point(1, 2);
let b = Point(1, 2);
let c = Point(3, 4);
println(a == b); // true
println(a == c); // false
println(a != c); // true
Structs in Zamin are reference types by default. Two variables assigned from the same struct instance will share the same data. Use explicit copying when independent copies are needed.
Error Handling
Zamin provides structured error handling with try/catch blocks and the throw keyword.
Throwing Errors
throw "something went wrong";
// Or use a structured error object
throw Error("invalid input");
Try / Catch
try {
let result = risky_operation();
println(result);
} catch (e) {
println(f"Error: {e}");
}
Error Propagation
When a function can fail, errors automatically propagate up the call stack unless caught. This lets you handle errors at the appropriate level.
func parse_number(s) {
let n = int(s);
if n == nil {
throw Error(f"'{s}' is not a valid number");
}
return n;
}
func process_input(input) {
let num = parse_number(input);
return num * 2;
}
try {
let result = process_input("42");
println(result); // 84
} catch (e) {
println(f"Failed: {e}");
}
try {
let result = process_input("abc");
println(result); // never reached
} catch (e) {
println(f"Failed: {e}"); // Failed: 'abc' is not a valid number
}
Prefer returning error values (like nil) for expected failures and throw for unexpected or programmer errors. This keeps error handling explicit and predictable.
Modules and Imports
Zamin's module system lets you split code across files and reuse shared libraries.
Importing Modules
import math
import string
import os
println(math.sqrt(16)); // 4.0
println(math.pi); // 3.14159...
let upper = string.to_upper("hello");
println(upper); // "HELLO"
Aliased Imports
import math as m
import string as str
println(m.sqrt(25)); // 5.0
println(str.to_upper("hi")); // "HI"
Creating Modules with Export
Mark items as export to make them available when the module is imported.
// my_module.zamin
export func greet(name) = f"Hello, {name}!";
export let VERSION = "1.0.0";
struct export User {
name,
email,
}
// main.zamin
import my_module
println(my_module.greet("Alice")); // "Hello, Alice!"
println(my_module.VERSION); // "1.0.0"
let user = my_module.User("Bob", "bob@example.com");
Module Loading Paths
Zamin searches for modules in these locations, in order:
- The current file's directory
- The project's
lib/directory - The Zamin standard library path
- Paths specified in
zamin.jsonconfiguration
Use the zamin project CLI to manage dependencies and module paths. See Project CLI for details.
Comments
Zamin supports single-line comments. Everything after // to the end of the line is ignored by the compiler.
// This is a comment
let x = 42; // Inline comment
// Multi-line comments can be done
// by writing multiple single-line comments
// like this.
Use comments to explain why something is done a certain way, not what the code does. Good code should be self-explanatory; comments are for context and intent.
Operators
Below is a complete reference of all operators in Zamin, listed from highest precedence to lowest.
| Precedence | Operator | Description |
|---|---|---|
| 1 | ** | Exponentiation |
| 2 | ~, - (unary) | Bitwise NOT, unary negation |
| 3 | *, /, //, % | Multiply, divide, integer divide, modulo |
| 4 | +, - | Addition, subtraction |
| 5 | <<, >> | Bitwise shift left, shift right |
| 6 | & | Bitwise AND |
| 7 | ^ | Bitwise XOR |
| 8 | | | Bitwise OR |
| 9 | .., ..= | Range (exclusive), range (inclusive) |
| 10 | ==, !=, <, >, <=, >= | Comparison |
| 11 | not | Logical NOT |
| 12 | and | Logical AND |
| 13 | or | Logical OR |
| 14 | in | Membership test |
| 15 | =, +=, -=, *=, /=, //=, %=, **= | Assignment and compound assignment |
// Arithmetic
println(2 ** 10); // 1024
println(7 / 2); // 3.5
println(7 // 2); // 3
println(-5.abs()); // 5
// Comparison
println(3 == 3); // true
println(3 != 4); // true
println(1 < 2); // true
// Logical
println(true and false); // false
println(true or false); // true
println(not false); // true
// Membership
println(3 in [1, 2, 3]); // true
println("a" in "cat"); // true
// Compound assignment
let x = 10;
x += 5;
x *= 2;
println(x); // 30
When in doubt about operator precedence, use parentheses to make your intent explicit. (a + b) * c is always clearer than relying on precedence rules.