Chapter 1.1: Hello World
Welcome to Ferrite! Let's start with the classic first program.
In Ferrite, you can use the built-in
println function to output text to the console.
println("Hello, World!");
Try in Playground →
Chapter 1.2: Variables & Types
Ferrite is strictly typed. You must declare the type of a
variable. By default, variables are declared with the
keep keyword.
keep age: int = 25;
keep name: string = "Ferris";
keep is_active: bool = true;
keep pi: float = 3.14159;
Variables are immutable constants by default. You can reassign them if you want, but their type can never change.
keep count: int = 1;
count = count + 1; // Allowed
// count = "two"; // Error: Type mismatch
Chapter 1.3: Functions & Closures
Functions in Ferrite use the fun keyword. You can declare parameters and return types explicitly. Ferrite also supports inline closures.
fun calculate_area(width: float, height: float) -> float {
return width * height;
}
// Closures use the arrow syntax and can capture variables
keep multiplier = 2.0;
keep scale = (x: float) => x * multiplier;
println("Area: " + str(calculate_area(10.0, 5.0)));
println("Scaled: " + str(scale(10.0)));
Chapter 2.1: If & Else
Conditional logic in Ferrite uses standard if,
else if, and else blocks. Note that
there are no parentheses around the condition! Because Ferrite is expression-oriented, you can assign the result of an `if` block directly.
keep score = 85;
keep grade = if score >= 90 {
"A"
} else if score >= 80 {
"B"
} else {
"C"
};
println("Grade: " + grade);
Chapter 2.2: Loops
Ferrite primarily uses while loops for iteration.
You can use skip to continue to the next
iteration, and stop to break out of the loop.
keep i = 0;
while i < 5 {
i = i + 1;
if i == 3 {
skip; // Skips printing 3
}
println("Count: " + str(i));
}
Chapter 3.1: Enums & Match
Ferrite supports powerful algebraic datatypes using
enum, and deep pattern matching with the
match keyword.
You can also use if clauses inside match arms as
"guards" for extra conditions! Match blocks evaluate to values, so they can be directly assigned.
enum Result<T> {
Ok(T);
Err(string);
}
keep response = Ok(200);
keep message = match response {
case Ok(status) if status == 200 => {
"Success!"
}
case Ok(status) => {
"Other status: " + str(status)
}
case Err(msg) => {
"Error: " + msg
}
};
println(message);
Chapter 4.1: Groups
Instead of classes or structs, Ferrite uses
group to define collections of data.
group Vector2 {
x: float;
y: float;
}
keep position = Vector2 { x: 10.5, y: 20.0 };
println("X Coordinate: " + str(position.x));
Chapter 4.2: Traits (Interfaces)
A trait defines shared behavior (like an
interface). You can then use the impl block to
implement that behavior for a specific group.
trait Display {
fun format(self) -> string;
}
impl Display for Vector2 {
fun format(self) -> string {
return "Vec2(" + str(self.x) + ", " + str(self.y) + ")";
}
}
println(position.format());
Chapter 4.3: Modules & Imports
Once your Ferrite program grows, you'll want to split it into multiple files. You can use import and from to manage scope.
Remember that all symbols are private by default, so you must use pub to expose them.
// geometry.fe
pub fun area(w: float, h: float) -> float {
return w * h;
}
// main.fe
import "geometry";
from "math" take { pi };
keep a = geometry.area(10.0, 5.0);
println("Area is: " + str(a));
Chapter 4.4: Native Lists (DSA)
Ferrite v3.1.0 introduces native dynamic collections! You can now use List<T> for data structures and algorithms, backed by automatic scope-based memory management (RAII).
keep stack: List<int> = List();
push(stack, 10);
push(stack, 20);
keep top = pop(stack);
println("Popped: " + str(top));
// Memory is automatically freed when `stack` goes out of scope!
Chapter 5.1: Tensors
Tensors are native primitives in Ferrite. When you declare a Tensor, you specify its shape in the type signature. The compiler checks these shapes during compile time!
import "math";
// Define a 1x4 input tensor and a 4x2 weights tensor
param inputs: Tensor<float, (1, 4)> = rand(1, 4);
param weights: Tensor<float, (4, 2)> = ones(4, 2);
// Matrix multiplication using the @ operator
// Resulting shape will automatically be (1, 2)
keep outputs = inputs @ weights;
Chapter 5.2: Execution Blocks
Ferrite uses specialized contexts for ML operations. For
example, infer {} blocks optimize execution for
pure feed-forward passes by disabling gradient tracking
overhead.
infer {
keep outputs = inputs @ weights;
println("Outputs: " + str(outputs));
}
train {
// Operations here will track gradients
keep loss = compute_gradients(inputs);
}
Chapter 6.1: Building a Neural Network
Ferrite makes machine learning models incredibly concise by treating tensors as first-class citizens. You don't need third-party libraries; neural networks can be expressed purely using Ferrite's native syntax.
Defining the Model Structure
struct MLP {
w1: Tensor, // 2D Weight matrix
b1: Tensor, // 1D Bias vector
w2: Tensor,
b2: Tensor,
}
impl MLP {
fun new() -> Self {
return MLP {
w1: randn(784, 128),
b1: zeros(128),
w2: randn(128, 10),
b2: zeros(10),
};
}
}
The Forward Pass
Because Ferrite natively supports tensor broadcasting and overloaded matrix multiplication (@), the forward pass reads exactly like the mathematical formula.
impl MLP {
fun forward(self, x: Tensor) -> Tensor {
// x @ w1 computes the matrix multiplication
// + b1 broadcasts the bias to the batch size automatically
keep hidden = (x @ self.w1 + self.b1).relu();
keep output = (hidden @ self.w2 + self.b2).sigmoid();
return output;
}
}
keep model = MLP::new();
keep dummy_input = randn(32, 784); // Batch of 32 images
keep predictions = model.forward(dummy_input);
println("Predictions shape: " + str(predictions.shape()));
🧠 AI-Native Advantage
Notice how we didn't have to import any external libraries. Matrix multiplication @ and activation functions like .relu() are built directly into the language, allowing Ferrite to perform compile-time shape checking and memory optimizations!