Functions are declared with the fn keyword. Every program needs a main function as its entry point.
#Declaring Functions
fn greet(name: Str) {
Println("Hello, {name}!");
}Parameters require explicit type annotations using the name: Type syntax.
#Return Types
Specify a return type with ->:
fn add(a: Int, b: Int) -> Int {
return a + b;
}Functions without a -> return type return nothing (void).
#Calling Functions
fn main() {
greet("World");
let result = add(3, 4);
Println(result); // 7
}#Multiple Parameters
fn clamp(value: Int, low: Int, high: Int) -> Int {
if value < low {
return low;
}
if value > high {
return high;
}
return value;
}#Multi-Return Functions
Functions can return multiple values using a parenthesized return type. The caller unpacks the result with tuple destructuring:
fn min_max(nums: Array(Int)) -> (Int,Int) {
var lo = nums[0];
var hi = nums[0];
for n in nums {
if n < lo { lo = n; }
if n > hi { hi = n; }
}
return lo, hi;
}
fn main() {
let (lo, hi) = min_max([3, 1, 4, 1, 5, 9]);
Println("{lo} {hi}"); // "1 9"
}The return a, b; syntax is shorthand for return Tuple(a, b);. The return type (T1, T2) is shorthand for Tuple(T1, T2). Both forms are equivalent:
fn divide(a: Int, b: Int) -> (Int,Int) {
return a / b, a % b; // quotient and remainder
}
fn swap(x: Str, y: Str) -> (Str,Str) {
return y, x;
}You can also use the full Tuple(...) syntax when you want to be explicit:
fn get_pair() -> Tuple(Int,Int) {
return Tuple(10, 20);
}#Default Arguments
Function parameters can specify default values using =. If a caller omits a default parameter, the default value is used:
fn greet(name: Str = "World") {
Println("Hello, {name}!");
}
fn main() {
greet(); // Hello, World!
greet("Zenth"); // Hello, Zenth!
}#Named Arguments
When calling a function, you can provide arguments by name, which allows you to pass them in any order. This is especially useful for functions with many parameters or default values:
fn draw_rect(x: Int, y: Int, width: Int, height: Int = 10) {
// ...
}
fn main() {
draw_rect(width=20, x=5, y=5); // y=5, x=5, width=20, height=10
}#Recursion
Functions can call themselves:
fn factorial(n: Int) -> Int {
if n <= 1 {
return 1;
}
return n * factorial(n - 1);
}#Built-in Functions
Zenth provides several built-in functions that are always available:
| Function | Description |
|---|---|
Print(x, [enabled]) | Print x without a newline; print only when enabled is true (default) |
Println(x, [enabled]) | Print x with a newline; print only when enabled is true (default) |
Len(x) | Return the length of a string or array |
Str(x) | Convert any value to its string representation |
Int(x) / Int(Str, base) | Convert to Int (optional base for string input) |
Float(x) | Convert to Float |
Ord(Str) / Chr(Int) | Convert between character and Unicode codepoint |
Abs(x) | Absolute value for Int or Float |
Min(a, b, ...) / Max(a, b, ...) | Minimum / maximum for two or more Int or Float values (all args must be the same type) |
Clamp(x, lo, hi) | Clamp x between lo and hi (Int or Float) |
Round(x) / Floor(x) / Ceil(x) | Floating-point rounding helpers (return Float) |
Pow(x, y) / Sqrt(x) | Power and square root (return Float) |
Range(start, end[, step]) | Build an exclusive range object |
Rangei(start, end[, step]) | Build an inclusive range object |
Hashmap(K, V[, default=val]) | Build an empty hashmap (optional default value) |
Set(T) / Set(items) | Build an empty set or convert an array to a set |
Tuple(a, b, ...) | Build a tuple value |
Zip(a, b, ...) | Combine two or more arrays into an array of tuples |
Env(name) / Env(name, default) | Read an environment variable (returns Str; default used when unset) |
Args.flag(default=val) | Declare a command-line flag with a default value |
Args.args() | Get remaining positional arguments as Array(Str) |
Date.today() | Get today’s date as a Date object |
Date.from(str[, fmt]) | Parse date from string (default format: %Y-%m-%d) |
File(path) | Build a file value for file methods |
Input(text) | Display editable pre-filled text and return the result (returns Str) |
Exit([code]) | Exit the program (0 when omitted) |
Assert(cond) / AssertEq(got, expected) | Built-in test/assertion helpers |
#Conversion Examples
fn main() {
let whole = Int("42");
let hex = Int("ff", 16);
let pi = Float("3.14159");
let ratio = Float(7) / Float(2);
Println("whole=" + Str(whole));
Println("hex=" + Str(hex));
Println("pi=" + Str(pi));
Println("ratio=" + Str(ratio));
}Character/codepoint helpers:
fn main() {
Println(Ord("A")); // 65
Println(Chr(66)); // "B"
}fn main() {
Println("hello"); // prints "hello\n"
Print("no newline"); // prints without newline
let debug = false;
Println("debug line", debug); // prints only when debug is true
Println(Len("abc")); // prints "3"
Println(Str(42)); // prints "42"
Println(Str(Abs(-5))); // prints "5"
}#Zip
The Zip() function combines two or more arrays into an array of tuples, pairing elements at corresponding positions:
let names = ["Alice", "Bob", "Charlie"];
let scores = [95, 87, 92];
for pair in Zip(names, scores) {
Println(pair.0 + ": " + Str(pair.1));
}
// Alice: 95
// Bob: 87
// Charlie: 92With three or more arrays:
let x = [1, 2, 3];
let y = [4, 5, 6];
let z = [7, 8, 9];
for t in Zip(x, y, z) {
Println(Str(t.0) + "," + Str(t.1) + "," + Str(t.2));
}When arrays have different lengths, Zip() stops at the shortest:
let a = [1, 2, 3, 4];
let b = [10, 20];
let zipped = Zip(a, b); // [Tuple(1, 10), Tuple(2, 20)]#Command-Line Arguments
The Args object provides access to command-line flags and positional arguments.
#Flags
Args.flag(default=val) declares a command-line flag. The flag name is inferred from the variable name, and the type is inferred from the default value (Bool, Int, or Str).
fn main() {
let debug = Args.flag(default=false); // --debug
let times = Args.flag(default=5); // --times 10
let msg = Args.flag(default="Hello"); // --msg "world"
for var i = 0; i < times; i++ {
Println(msg, !debug);
}
}Build and run with flags:
zenth build -o greet greet.zn
./greet --debug --times 3 --msg "Hi"Or pass flags directly with zenth run:
zenth run greet.zn --times 3 --msg "Hi"When no flags are provided, the default values are used. Boolean flags are set to true by passing --name with no value.
#Positional Arguments
Args.args() returns an Array(Str) of all remaining positional arguments (those not consumed by flags):
fn main() {
let verbose = Args.flag(default=false);
let args = Args.args();
for file in args {
Println("Processing: {file}");
}
}./program --verbose file1.txt file2.txt
# args = ["file1.txt", "file2.txt"]Positional arguments are everything that appears after the flags (or after -- to force the end of flag parsing).
#Environment Variables
The Env() built-in reads environment variables. It always returns a Str.
fn main() {
// Read an environment variable (empty string if not set)
let home = Env("HOME");
Println("Home: " + home);
// Provide a default value for when the variable is unset or empty
let editor = Env("EDITOR", "vim");
Println("Editor: " + editor);
// Use in conditionals
let debug = Env("DEBUG", "false");
if debug == "true" {
Println("Debug mode enabled");
}
}With one argument, Env(name) returns the value of the environment variable, or an empty string "" if it is not set. With two arguments, Env(name, default) returns the default value when the variable is unset or empty.
#User Input
The Input() built-in displays editable pre-filled text in the terminal and returns the final text after the user presses Enter. The user can move the cursor with arrow keys, edit the text with backspace/delete, and use Ctrl-A/Ctrl-E for Home/End.
fn main() {
let name = Input("World");
Println("Hello, {name}!");
}The text World appears pre-filled and editable. The user can accept it as-is by pressing Enter, or modify it first:
World <- user sees this, cursor at end, can edit
Hello, World! <- output if accepted unchangedUse Input() for interactive programs where you want to suggest a default value the user can tweak:
fn main() {
let city = Input("New York");
let country = Input("US");
Println("{city}, {country}");
}You can use the result in any expression that expects a Str:
fn main() {
let age = Int(Input("25"));
if age >= 18 {
Println("You are an adult.");
} else {
Println("You are a minor.");
}
}#Keyboard shortcuts
| Key | Action |
|---|---|
| Enter | Accept text |
| Left/Right arrows | Move cursor |
| Backspace | Delete character before cursor |
| Delete | Delete character at cursor |
| Ctrl-A / Home | Move to start |
| Ctrl-E / End | Move to end |
| Ctrl-U | Clear entire line |
| Ctrl-C | Cancel (exit program) |
When stdin is not a terminal (e.g. piped input), Input() falls back to returning the initial text unchanged.
#Naming Rules
Zenth reserves leading uppercase for system names:
- Built-in functions are capitalized (
Println,Len,Range, etc.). - User-defined top-level functions must start with a lowercase letter.
- User-defined variables (
let,var,const, including destructuring and loop bindings) must start with a lowercase letter.
Examples:
fn build_report() { // ok
let debug = Args.flag(default=false); // ok
let value = 10; // ok
}
fn BuildReport() { } // error
let Debug = true; // error
const MaxSize = 1024; // error#Closures
Closures are anonymous functions declared with fn in expression position. They are used with array methods like .map() and .filter():
let doubled = [1, 2, 3].map(fn(x) x * 2);
let evens = [1, 2, 3, 4].filter(fn(x) x % 2 == 0);Parameter types are inferred from context when used with .map() or .filter(). You can also provide explicit types:
fn(x: Int) -> Int x * 2Block body closures use explicit return:
let processed = nums.map(fn(x: Int) -> Int {
let y = x + 10;
return y;
});#Function Types
Functions can be passed as arguments to other functions using function type annotations. The syntax for a function type is Fn(ParamTypes) -> ReturnType:
fn apply(x: Int, f: Fn(Int) -> Int) -> Int {
return f(x);
}
fn double(x: Int) -> Int {
return x * 2;
}
fn main() {
// Pass a named function
Println(apply(5, double)); // 10
// Pass a closure
Println(apply(5, fn(x) x + 10)); // 15
}#Function Type Syntax
Function types use Fn(ParamTypes) -> ReturnType. Omit the -> ReturnType for void functions:
Fn(Int) -> Int // takes Int, returns Int
Fn(Str, Int) -> Bool // takes Str and Int, returns Bool
Fn(Int) // takes Int, returns nothing (void)
Fn() -> Str // takes nothing, returns StrNote: Fn (capitalized) is the type name, while fn (lowercase) is the keyword for declaring functions and closures.
#Higher-Order Functions
You can write your own functions that accept function parameters:
fn count_matching(items: Array(Int), pred: Fn(Int) -> Bool) -> Int {
var count = 0;
for item in items {
if pred(item) {
count++;
}
}
return count;
}
fn is_even(x: Int) -> Bool {
return x % 2 == 0;
}
fn main() {
let nums = [1, 2, 3, 4, 5, 6];
Println(count_matching(nums, is_even)); // 3
Println(count_matching(nums, fn(x) x > 3)); // 3
}#Function-Typed Variables
Variables can hold function values:
let f: Fn(Int) -> Int = double;
Println(f(7)); // 14#Passing Functions to map/filter/reduce
Named functions can be passed directly to .map(), .filter(), and .reduce():
fn double(x: Int) -> Int { return x * 2; }
fn is_even(x: Int) -> Bool { return x % 2 == 0; }
fn add(a: Int, b: Int) -> Int { return a + b; }
fn main() {
let nums = [1, 2, 3, 4, 5];
let doubled = nums.map(double); // [2, 4, 6, 8, 10]
let evens = nums.filter(is_even); // [2, 4]
let total = nums.reduce(add, 0); // 15
}#Methods
Functions can be defined inside objects to act as methods. See Objects for details.