Get Started

Your first steps with Ferrite.

1

Installation

Install Ferrite using the appropriate method for your OS from the Downloads page.

Verify installation:

ferrite --version
2

Write Code

Create a new file named hello.fe and add the following:

keep message: string = "Hello, Ferrite!";
println(message);
3

Run

Execute your program using the interpreter:

ferrite run hello.fe

Expected output: Hello, Ferrite!

Ferrite Tutorial

Learn the language from scratch.

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!