Talor
Internal beta The compiler is not public yet. A public beta is coming. See where it stands.

Memory safety you don’t have to spell out.

Talor is a systems language with static memory safety and no garbage collector. Regions are inferred rather than annotated, a borrow needs no sigil and no lifetime written (string, never &string), and what a function does to its caller's values is one optional clause after the signature instead of a mode keyword on every parameter.

There is no runtime ownership check: the compiler proves it, emits LLVM IR, and gets out of the way.

The Talor mascot, a turquoise hedgehog

What it looks like

Everything on this page compiles and runs today, checked by the compiler before the page was built.

use std.map.{Map};
use std.math.{PI};

variant Shape { Dot, Circle(f64), Rect { w: f64, h: f64 } }

struct Counter { seen: Map<string, i64> }

fn area(s: Shape): f64 {
    match s {
        Shape.Dot => 0.0,
        Shape.Circle(r) => PI * r.powi(2),
        Shape.Rect { w, h } => w * h,
    }
}

impl Counter {
    fn empty(): Counter { Counter { seen: Map.empty() } }

    fn note(self, name: string) {
        let before = match self.seen.get(name.clone()) { Some(n) => n, None => 0 };
        self.seen.set(name, before + 1);
    }
}

fn main(): i32 {
    let c = Counter.empty();
    c.note("circle");
    c.note("rect");

    let shapes = [Shape.Circle(1.0), Shape.Rect { w: 2.0, h: 3.0 }, Shape.Dot];
    let total = 0.0;
    for s in shapes { total += area(s); }

    println(`${c.seen.len()} kinds, area ${total}`);
    0
}
A tagged union, a map from the standard library, and a method that says what it does to the values it is given.

A borrow needs no sigil

string, never &string, and nothing written for the borrow itself: a place handed to a function that only reads it becomes a borrow of that place, so a call site carries nothing, and regions are inferred - no lifetime parameters to write and none to read.

Nothing to annotate

Types, regions and what a function does to a parameter are inferred: edits self is how an author states an effect, and a function that says nothing is checked exactly as strictly.

No move operator, no mut

A move is a consuming use, and the compiler knows one when it sees one: the last read of a place takes it rather than copying it.

variant and enum

Two declarations for two different things: a tagged union and a numbered set. A recursive variant needs no Box.

Ownership, spelled with fewer marks

// A borrow has no sigil: `string`, never `&string`. Nothing is written for the
// borrow itself - a place handed to a function that only reads it becomes a
// borrow of that place, and the callee reads it where it lies.
fn longest_len(a: string, b: string): i64 {
    if a.len() >= b.len() { a.len() } else { b.len() }
}

// The clause after a signature says what a function does to the caller's
// values. `edits` mutates, `takes` keeps. Both are optional, and a function
// that says nothing is checked exactly as strictly.
fn push_twice(xs: Array<i64>, v: i64) {
    xs.push(v);
    xs.push(v);
}

fn consume(name: string) {
    println(name);
}

// There is no move operator and no `mut`. A move is a consuming use, and the
// compiler knows one when it sees one: the last read of a place takes it
// rather than copying it.
fn main(): i32 {
    let xs = [1, 2];
    push_twice(xs, 7);
    consume("kept");
    let a = "abc";
    let b = "de";
    // Nothing is written at the call site: `a` and `b` are read, not copied.
    println(`${xs.len()} items, longest ${longest_len(a, b)}`);
    0
}
The compiler refuses what it cannot prove, and says what to write instead: a function that hands back a read of two different arguments is an error, not an annotation to add.

Two kinds of sum type

// `variant` is the tagged union and `enum` is the numbered set: two
// declarations, because they are two different things.
use std.math.{PI};

variant Shape { Dot, Circle(f64), Rect { w: f64, h: f64 } }

enum Level { Low, Medium, High }

fn area(s: Shape): f64 {
    match s {
        Shape.Dot => 0.0,
        Shape.Circle(r) => PI * r.powi(2),
        Shape.Rect { w, h } => w * h,
    }
}

// An `enum` case carries no data and answers the number it was given.
fn threshold(l: Level): i64 {
    match l {
        Level.Low => 10,
        Level.Medium => 100,
        Level.High => 1000,
    }
}

fn main(): i32 {
    println(`${area(Shape.Rect { w: 2.0, h: 3.0 })} and ${threshold(Level.High)}`);
    0
}
Map is not built in either: it is ordinary Talor over Array and the structural hash.

Get started

Not yet : and this section says so rather than pretending otherwise. Talor is in internal beta: the compiler builds, passes its gates and compiles itself, but the source is not published, so there is nothing to clone today. This is what the first public beta will look like.

# when the public beta lands:
git clone <repository> talor && cd talor
make                             # the C core and talorboot
make stage1                      # build/stage1/talor-stage1
make verify-stage1               # gen2 == gen3: the compiler is a fixpoint of itself
make install                     # puts it on PATH as `talor`

talor run program.talor
What make install puts on your PATH is gen2, the compiler the language compiled, not the one the C seed compiled.

Editor support

The grammar is published on npm, so a site, a documentation build or an editor can highlight .talor source without vendoring a copy. This page is coloured by it.

npm install talorlang

Status

Availability
internal beta; a public beta of the language and compiler is coming
Self-hosting
reached; gen2 == gen3, byte-identical IR
Specification
Core v0, frozen; both compilers implement it
Backend
LLVM IR
Platforms
Linux and macOS; Windows in progress
Licence
MIT OR Apache-2.0, at your option