Five-minute tour
Each step is one complete idea, and every sample is Core v0, the subset the self-hosted compiler is written in.
1. Names, and what they may do
const LIMIT: i64 = 10; // module level, computed at compile time
fn fibonacci(n: i64): i64 {
if n < 2 { return n; }
let prev: i64 = 0; // a local the body writes is mutable
let curr: i64 = 1;
let i: i64 = 2;
while i <= n {
let next = prev + curr; // never written, so never mutable
prev = curr;
curr = next;
i += 1;
}
curr // the tail expression is the value
}if, while and blocks need no parentheses around their condition, but braces are mandatory on every body. A function's value is its last expression, without ;.
2. Text
fn describe(n: i64): string {
let size = if n < 10 { "small" } else if n < 100 { "medium" } else { "large" };
let parity = { let r = n % 2; if r == 0 { "even" } else { "odd" } };
`${n} is ${size} and ${parity}`
}if and a block are expressions. A template literal is the only formatting mechanism: each ${...} holds an expression, and the pieces are concatenated into a string.
3. One value, one of several shapes
use std.math.{PI};
variant Shape {
Circle(f64),
Rect { w: f64, h: f64 },
Dot,
}
fn area(s: Shape): f64 {
match s {
Shape.Circle(r) => PI * r.powi(2),
Shape.Rect { w, h } => w * h,
Shape.Dot => 0.0,
}
}A variant is a tagged union: a value is exactly one case, each case carries its own data, and the only way to read it is a match, which must be exhaustive. Member access is a dot, always: Shape.Circle(1.0). There is no ::.
4. Absence and failure are values
fn parse_count(s: string): Result<i64, string> {
match s.to_i64() {
Some(n) => Ok(n * 2),
None => Err(`not a number: ${s}`),
}
}
fn double_or_fail(s: string): Result<i64, string> {
let n = parse_count(s)?; // returns the Err from here on Err
Ok(n)
}There is no null and no exception: absence is Option<T> (or T?), failure is Result<T, E>, and ? propagates.
5. What a function does to your values
use std.math.{PI};
// Shape and area, as step 3 wrote them.
variant Shape {
Circle(f64),
Rect { w: f64, h: f64 },
Dot,
}
fn area(s: Shape): f64 {
match s {
Shape.Circle(r) => PI * r.powi(2),
Shape.Rect { w, h } => w * h,
Shape.Dot => 0.0,
}
}
fn append(dst: Array<i64>, src: Array<i64>) {
for v in src {
dst.push(v);
}
}
fn main(): i32 {
let shapes: Array<Shape> = [Shape.Circle(2.0), Shape.Rect { w: 3.0, h: 4.0 }];
let areas: Array<i64> = [];
for s in shapes {
areas.push(area(s) as i64);
}
append(areas, [100, 200]);
println(`areas: ${areas.len()}`);
0
}Parameters borrow by default, and the compiler reads the body for the rest: append pushes into dst, so dst is edits and the argument has to be a place that may be written; a function that keeps its argument is takes, and using the value afterwards is an error that names the call. Nothing is written at the call site, ever. A clause after the signature (edits dst, takes src) states that effect when the author wants it said out loud; it is optional, and a function that says nothing is checked exactly as strictly.
6. Types of your own
use std.list.{filter};
struct Account { owner: string, cents: i64 }
impl Account {
fn deposit(self, amount: i64) { self.cents += amount; }
fn label(self): string { `${self.owner}: ${self.cents} cents` }
}
trait Describe { fn describe(self): string; }
impl Describe for Account { fn describe(self): string { self.label() } }
fn largest<T: Describe>(items: Array<T>, size: fn(T): i64): string {
let best = 0;
let text = "";
for it in items {
if size(it) > best { best = size(it); text = it.describe(); }
}
text
}
fn main(): i32 {
let a = Account { owner: "ana", cents: 500 };
a.deposit(250);
let all = [a, Account { owner: "bia", cents: 900 }];
let rich = filter(all, (x) => x.cents > 600);
println(largest(rich, (x) => x.cents));
0
}Methods live in an impl block, never inside the struct, and deposit writes self, which is all it takes for the call to need a value that may be written. A trait is implemented by name, a generic parameter states the traits it needs (T: Describe), and a closure such as (x) => x.cents is a value whose parameter type the call infers.