Defining functions
Functions are the building blocks of Quadrate programs. They transform values on the stack.
Basic syntax
fn name(inputs -- outputs) {
// body
}
Every function has:
- A name
- A signature describing stack effects
- A body with the implementation
Your first function
fn square(x:i64 -- result:i64) {
x x *
}
fn main() {
5 square print nl // 25
}
The signature (x:i64 -- result:i64) means:
- Input: Takes one integer
xfrom the stack - Output: Leaves one integer
resulton the stack
Stack effect signature
The -- separates inputs from outputs:
fn sum(a:i64 b:i64 -- result:i64) {
a b +
}
fn swap_print(a:i64 b:i64 -- ) {
b print nl a print nl
}
fn push_two( -- a:i64 b:i64) {
1 2
}
| Signature | Meaning |
|---|---|
(a:i64 -- b:i64) |
1 input, 1 output |
(a:i64 b:i64 -- c:i64) |
2 inputs, 1 output |
(x:i64 --) |
1 input, no outputs |
(-- x:i64) |
No inputs, 1 output |
() |
No inputs, no outputs |
Parameter names
Named input parameters are automatically bound as local variables:
use math
fn distance(x1:f64 y1:f64 x2:f64 y2:f64 -- d:f64) {
x2 x1 - dup * // (x2-x1)^2
y2 y1 - dup * // (y2-y1)^2
+ math::sqrt
}
Simply use the parameter names to refer to their values:
fn add(a:i64 b:i64 -- sum:i64) {
a b +
}
Unnamed parameters
Parameters can omit names — they stay on the stack for direct manipulation:
fn double(i64 -- i64) {
2 * // operates directly on the stack value
}
fn add(i64 i64 -- i64) {
+ // adds the two values on the stack
}
When to use which:
- Named (a:i64) — when you reference the value multiple times or the meaning isn't obvious
- Unnamed (i64) — for simple stack operations where the body is a single expression
The same applies to return types: name them for clarity in public APIs (-- result:i64), leave unnamed in lambdas and trivial functions (-- i64).
Multiple outputs
Functions can have multiple outputs:
fn divmod(a:i64 b:i64 -- quotient:i64 remainder:i64) {
a b / // quotient
a b % // remainder
}
fn main() {
17 5 divmod
print nl // 2 (remainder)
print nl // 3 (quotient)
}
Functions without parameters
fn greet() {
"Hello, World!" print nl
}
fn get_answer( -- answer:i64) {
42
}
fn main() {
greet
get_answer print nl // 42
}
The main function
Every program needs a main function:
fn main() {
// Program starts here
}
main takes no inputs and has no outputs.
Function names
Rules for function names:
- Start with a letter or underscore
- Can contain letters, numbers, underscores
- Case-sensitive (
fooandFooare different)
fn calculate_total() { }
fn processItem2() { }
Type annotations
Always specify types for parameters:
| Type | Description |
|---|---|
i64 |
64-bit integer |
f64 |
64-bit float |
str |
String |
ptr |
Pointer |
any |
Any type |
Booleans are represented as i64 (0 = false, non-zero = true).
fn format_price(price:f64 currency:str -- formatted:str) {
// ... implementation using price and currency
}
Documentation comments
Use /// for documentation:
/// Calculates the factorial of n
/// @param n The number to calculate factorial of
/// <<output The factorial result
fn factorial(n:i64 -- result:i64) {
n 1 <= if {
1
} else {
n n 1 - factorial *
}
}
Inline functions
The inline keyword requests the compiler to inline the function body at call sites, eliminating function call overhead. The compiler will inline in most cases, but may decline for recursive functions or indirect calls.
inline fn double(x:i64 -- result:i64) {
x 2 *
}
Can be combined with pub:
pub inline fn inc(x:i64 -- result:i64) {
x 1 +
}
Use inline for small helper functions where the call overhead would dominate the actual work. The standard library's sys module uses this for zero-overhead hardware access wrappers.
What's next?
Now let's learn how to Call Functions and chain them together.