Tutorial: Variables, Types, and Strings
This tutorial starts from the smallest possible program and builds up to the core ideas you will use in every Metel program: variables, type annotations, mutability, and strings.
The entry point
Every Metel program starts in main:
fun main() {
println("hello, Metel");
}
fun declares a function. main is the only name the interpreter looks for. println is a built-in that prints its argument followed by a newline. Save this as hello.mtl and run it:
cargo run -- hello.mtl
Output:
hello, Metel
Start Small
If a program does not run, reduce it back to a tiny main like this first. It gives you a known-good baseline before adding more syntax.
Variables
let creates a binding. The type is inferred from the right-hand side:
fun main() {
let x = 42;
let greeting = "hello";
let ratio = 1.5;
let flag = true;
println(x);
println(greeting);
println(ratio);
println(flag);
}
println accepts any type that can be displayed — integers, floats, booleans, and strings all work directly.
Inference First
Metel will usually infer the type you expect from the right-hand side. Add an annotation when it improves clarity, not by default.
Type annotations
Annotations are optional but you can write them after the binding name with ::
fun main() {
let x: i64 = 42;
let greeting: String = "hello";
let ratio: f64 = 1.5;
}
Write annotations when they make the intent clearer, or when you want the compiler to catch an unexpected type mismatch early.
Metel uses explicit numeric types. The common defaults are i64 for integers and f64 for floating-point values, and you can also choose exact-width numeric types such as i8, i16, i32, u8, u16, u32, u64, f32, and f64 when the bit width matters. Unsuffixed literals like 42 or 3.14 automatically adopt the type the annotation demands:
fun main() {
let pixels: u8 = 255; // 255 is u8
let count: i32 = 1000; // 1000 is i32
let ratio: f32 = 1.5; // 1.5 is f32
}
See Sized Numeric Types for the full list.
Mutable variables
let bindings are immutable by default. To rebind a variable, use let mut:
fun main() {
let mut count = 0;
count = count + 1;
count = count + 1;
println(count); // 2
}
let and mut are not types — together they describe a mutable binding. Compound assignment operators work on let mut variables too:
fun main() {
let mut total = 10;
total += 5;
total -= 2;
total *= 3;
println(total); // 39
}
mut marks a mutable binding. It allows reassignment and is also part of whether operations that require mutable access, such as taking &mut to a binding or mutating through its storage, are permitted.
Strings
Strings are double-quoted UTF-8 values. You can concatenate them with +:
fun main() {
let first = "hello";
let second = "world";
let combined = first + ", " + second + "!";
println(combined); // hello, world!
}
String interpolation
Instead of manually concatenating, you can embed expressions directly inside a string with ${…}:
fun main() {
let name = "Ada";
let score = 98;
let message = "Player ${name} scored ${score} points.";
println(message); // Player Ada scored 98 points.
}
Anything inside ${…} must be a type that has a .to_string() method — built-in scalars such as integers, floats, boolean, String, and Char satisfy this. You can also use full expressions:
fun main() {
let a = 6;
let b = 7;
println("${a} × ${b} = ${a * b}"); // 6 × 7 = 42
}
Converting values to strings explicitly
Every built-in type has a .to_string() method:
fun main() {
let n: i64 = 42;
let f: f64 = 3.14;
let b: boolean = true;
let s = n.to_string() + " / " + f.to_string() + " / " + b.to_string();
println(s); // 42 / 3.14 / true
}
You will need .to_string() when building strings in contexts where the interpolation syntax is not available, such as inside a function that returns String.
A complete example
Here is a short program that puts variables, arithmetic, and strings together:
fun main() {
let name = "Metel";
let major = 0;
let minor = 7;
let patch = 0;
let version = "${major}.${minor}.${patch}";
let banner = "Welcome to ${name} v${version}";
println(banner);
let mut items = 0;
items += 3;
items += 2;
let summary = "Processed ${items} items in this run.";
println(summary);
}
Output:
Welcome to Metel v0.8.0
Processed 5 items in this run.
What you learned
fun main()is the program entry point.letcreates an immutable binding;mutallows reassignment.- Types are inferred — annotations are optional but always accepted.
printlnprints any built-in type directly.+concatenates strings;${expr}embeds expressions inside string literals.
Next: Numbers, Characters, and Collections — exact-width numerics, Char, arrays, List<T>, and turbofish.