Peeper

Language tour

Functions, variables, loops, arrays, structs, borrows and panics, each with what it prints.

Each program here runs as shown with peeper run; the box under it is what it prints. The compiler repository's examples/ folder has a longer program for each part.

Functions and constants

A function takes typed parameters and may give a result. The program starts at main; an i32 result is its exit code. A constant's value is computed while compiling, and its type can come from where it is used.

const Limit = 10;                   // its type comes from where it is used
const Answer: i32 = Limit * 4 + 2;  // computed while compiling

fn square(x: i64) -> i64 {
    return x * x;
}

fn gcd(a: i64, b: i64) -> i64 {
    if b == 0 {
        return a;
    }
    return gcd(b, a % b);
}

fn main() -> i32 {
    println("square:", square(Limit));
    println("gcd:", gcd(48, 18));
    return Answer - 42;             // the exit code: 0
}
square: 100
gcd: 6

Variables

let names a value and mut lets it change. A let without a value is set by its first assignment, and the compiler checks it is set once on every path before it is read.

fn sign(value: i32) -> i32 {
    let result: i32;                // set once on every path below
    if value > 0 {
        result = 1;
    } else if value < 0 {
        result = -1;
    } else {
        result = 0;
    }
    return result;
}

fn main() {
    let fixed = 5;                  // `i32`, from the value
    let mut total: i64 = 0;
    total = total + fixed;
    println("total:", total, "signs:", sign(-8), sign(0), sign(3));
}
total: 5 signs: -1 0 1

Loops and ranges

for is the only loop. It goes over a range, an array or a slice, runs while a condition holds, or runs until break. a..b counts up to b, a..=b takes b too, :step steps by more than 1, and a range counts down when a > b.

fn main() {
    print("step:");
    for v in 0..10:3 { print("", v); }
    println();

    print("down:");
    for v in 5..=0:2 { print("", v); }
    println();

    let mut n = 1;
    for n < 100 {                   // while `n < 100`
        n = n * 3;
    }
    println("while:", n);
}
step: 0 3 6 9
down: 5 3 1
while: 243

Arrays and references

[N]T holds N values of type T, and .len is N. An array copies when assigned or passed. & takes a reference, &mut one that can change the value, and .* reaches the value. values... spreads one array's values into another.

fn bump(values: &mut [3]i32) {      // changes the caller's array
    for value in values {
        value.* = value.* + 1;
    }
}

fn main() {
    let a = [3]i32{1, 2, 3};
    let mut c = a;                  // a copy: `a` stays as it is
    c[0] = 9;
    bump(&mut c);
    println("a:", a, "c:", c, "len:", c.len);
    println("spread:", [_]i32{a..., 0, a...});
}
a: [1, 2, 3] c: [10, 3, 4] len: 3
spread: [1, 2, 3, 0, 1, 2, 3]

Structs and moves

Name.{ field = value } makes a struct, with the fields in any order, and .{ ... } takes the type from where it goes. A struct moves when assigned or passed: the old variable is then unset.

struct Point { x: i32, y: i32 }
struct Line { start: Point, end: Point }

fn length(line: Line) -> i32 {      // takes the line, moved in
    return line.end.x - line.start.x + line.end.y - line.start.y;
}

fn main() {
    let start = Point.{ y = 2, x = 1 };
    let end: Point = .{ x = 4, y = 6 };
    let line = Line.{ start = start, end = end };
    println("line:", line);
    println("length:", length(line));   // `line` moves; it is unset now
}
line: Line { start: Point { x: 1, y: 2 }, end: Point { x: 4, y: 6 } }
length: 7

Borrows

A &mut borrow may not overlap a reference that is still in use. The compiler names all three places: where the reference was made, where the &mut borrow starts, and where the reference is used later.

fn update(value: &mut i32, by: i32) {
    value.* = value.* + by;
}

fn main() {
    let mut count = 1;
    let view = &count;
    update(&mut count, 2);
    println("count:", view.*);
}
peeper check
[T0037]: `count` is borrowed as `&mut` while a reference to it is still in use  --> src/main.peep:8:12  | 6 |     let mut count = 1;7 |     let view = &count;  |                ------ reference made here8 |     update(&mut count, 2);  |            ^^^^^^^^^^ borrowed as `&mut` here9 |     println("count:", view.*);  |                       ------ reference used later here  |   = note: a `&mut` argument takes hold when the call startsCompilation failed with 1 error(s)

Checks while running

What the compiler cannot know, the program checks when it runs: an index against the length, a division by zero, a shift past the width. A failed check stops the program with exit code 101 and says where.

fn main() {
    let values = [3]i32{4, 8, 15};
    for i in 0..4 {
        println("value:", values[i]);
    }
}
value: 4
value: 8
value: 15
panic: index out of bounds: the length is 3
  --> src/main.peep:4:27

Next

Each part of the language has its own page, with a whole program and what it prints. Start with Functions and constants.

On this page