Quick Reference

This page covers the entire language with brief examples for those who want a quick tour. Links to the full reference are left at each section.

More examples can be found on the examples page.

Variables

See Variables & Constants.

let x = 0;           // inferred as int
let y: float = 1.5;  // or annotate the type explicitly
x = 10;              // `let` bindings are reassignable
const MAX = 100;     // `const` is not, and must be compile-time known

let x = "a str";     // shadowing: reuse a name, even with a new type

There is no mut and no borrow checker — a managed runtime handles memory, so the binding model is just let (reassignable) and const (not).

Primitive types

See Primitive Types.

let i: int   = 1_000; // 64-bit signed; underscores ignored; 0xff hex too
let f: float = 3.14;  // 64-bit IEEE-754
let b: bool  = true;
let s: str   = "hi";  // no char type; a single character is a 1-char str

// int + float promotes to float; there is no `as` cast — use methods
let g: float = i.to_float();
let back: int = g.to_int(); // truncates toward zero

Integer arithmetic is checked: overflow is a runtime error, never a silent wrap.

Operators

See Operators.

// arithmetic — / always yields float, ~/ truncates, % is remainder
7 + 2;   7 - 2;   7 * 2;   7 / 2;   7 ~/ 2;   7 % 2;
"ab" + "cd";  // -> "abcd"  (+ concatenates strings)

a < b;   a == b;   a != b;   // comparison -> bool (ordering: numbers only)
x && y;  x || y;  !x;        // logical, short-circuiting
a & b;   a | b;   a ^ b;   a << 1;   a >> 1; // bitwise (int)

a += 1;  a *= 2;  a ~/= 2;  a ??= z; // every binary op has a compound form

Strings

See Primitive Types.

let multi = "spans
multiple lines";  // the newline becomes part of the value

let name = "world";
let msg = f"hi {name}, 1+1={1 + 1}"; // f-string interpolates any expression
let lit = f"{{literal braces}}";     // -> "{literal braces}"

let clean = "  Hi  ".trim().to_lower(); // str methods chain

Collections

See Collections.

// Array — ordered, growable, single element type: [T]
let xs: [int] = [3, 1, 2];
xs.push(4);
let first = xs[0]; // 0-based; reading past the end faults at runtime

// Dictionary — hash map of str keys to one value type: ~{V}
// (the leading ~ tells it apart from a block)
let scores: ~{int} = ~{ alice = 10, bob = 7 }; // keys are bare idents
let a: int? = scores["alice"]; // indexing yields V? (missing key -> null)
scores.insert("cy", 3);

// Tuple — fixed-length, heterogeneous, indexed by position
let pair: (int, str) = (1, "one");
let one = pair.1;    // -> "one"
let (x, y) = (3, 4); // destructuring let

// `in` tests membership
0 in xs;            // array: contains the element
"alice" in scores;  // dict: has the key
"ell" in "hello";   // str: substring

Control flow

See Control Flow.

Everything here is an expressionif, match, blocks, and loops all yield values. Block braces are optional around a single expression.

if x == 0 {
    print("zero");
} else if x > 0 {
    print("positive");
} else {
    print("negative");
}

let sign = if x >= 0 1 else -1; // as an expression, braces optional
// an `if` with no `else` must yield () — its body can't produce a value

let total = {     // blocks yield their final semicolon-free expression
    let a = 3;
    a + 5         // -> 8
};
// match — first arm to fit wins; exhaustiveness is checked (Maranget-style).
// the arms below show the pattern kinds, not one real scrutinee.
let label = match value {
    0 => "zero",                  // literal
    1 | 2 | 3 => "small",         // or-pattern (multiple literals)
    n if n > 100 => "big",        // guard
    found? => found.name,         // null-bind: binds when value isn't null
    Point { x = 0, y } => f"{y}", // struct destructure (fields use `=`)
    Shape::Circle(r) => area(r),  // enum-variant destructure
    (a, b) => a + b,              // tuple destructure
    other => fallback(other),     // bare ident binds anything; `_` too
};

let code = match cmd {  // end with a bare `!` to promise exhaustiveness;
    "go" => 1,          // reaching it is a runtime fault
    "stop" => 2,
    !
};
loop {                 // infinite until `break`
    if done() { break; }
    continue;
}
let answer = loop { break 42; }; // `loop` can `break value` -> 42

while ready() { work(); }
for item in xs { print(item); }  // iterates arrays, dicts, str, 0..n on int

// `collect` turns any loop into an array builder — mimas's comprehension
let doubled = for n in xs collect n * 2;
let evens   = for n in xs { if n % 2 == 0 collect n; }; // filter with `if`

Options

See Options.

A value that may be null is an option, written T?. null exists only where a ? invites it, so the compiler guarantees you never hit an unexpected null.

let maybe: int? = lookup(); // an int, or null
// let bad = maybe + 1;     // compile error: maybe may be null

let n = maybe ?? 0;     // ??  fallback when null
let len = name?.len();  // ?.  short-circuits to null on a null receiver
let cell = grid[1]?[0]; // ?[] the same, for indexing
let sure = maybe!;      // !   asserts non-null (faults if it was null)

if let n = maybe {      // bind the unwrapped value when present
    handle(n);
} else {
    fallback();
}

let n? = maybe else { return; }; // let/else: bind, or diverge and move on

T?? automatically flattens to T? — there is no option-of-an-option.

Results & errors

See Results & Error Handling.

Two tiers of failure: an unrecoverable panic that halts the VM, and a recoverable result (T!) a caller can handle.

panic("unreachable"); // halts the VM immediately
todo("later");        // panic's cousin for unfinished code (msg optional)

fn parse_port(s: str) -> int! { // `!` return type lets the fn `raise`
    let n = s.to_int();         // str.to_int() -> int?
    if n == null {
        raise "not a number";   // the raised value must be a str*
    }
    n!                          // a bare T auto-wraps as the Ok case
}

// unwrap with `!`: take the value, or fault hard if it raised
let p: int = parse_port("80")!;

// recover with `absolve`: handle the error string, keep running
let q: int = parse_port(input) absolve |e| {
    print(f"bad: {e}");
    8080
};

panic, todo, return, raise, and an endless loop {} are all never (!) typed, so they slot into any branch without disturbing its type.

*typed errors are coming

In 0.1.0 the error carried by a result is always a str. The plan is to move to an Error pact you can implement for your own types, so failures can carry structured data. For now, a descriptive message is the tool.

Functions & closures

See Functions & Closures.

fn square(n: int) -> int { n * n }   // body is a block; last expr returns
fn greet(name: str) { print(name); } // no `->`: returns ()

// optional params (with const-foldable defaults) follow the required ones
fn connect(host: str, port=8080) {}
connect("localhost");            // port defaults to 8080
connect("localhost", port=9090); // name an optional to skip earlier ones

// closures: inline, anonymous, pipe-delimited; return type inferred
let add = |a: int, b: int| a + b;
let now = || current_time();     // no args

fns live only at the file’s top level and capture nothing — they’re second-class (passable as arguments, but not bindable). Closures are first-class (bindable, storable) and capture their surrounding scope.

let g = add;       // ok — closures are values
// let h = square; // error — a fn isn't a value you can bind

Structs

See Structs.

struct Player { name: str, score: int }

let p = Player { name = "ada", score = 0 }; // construct with `=`
print(p.name);                              // read fields with `.`

impl Player {                  // a type may have many impl blocks
    const MAX = 100;           // associated const
    fn new(name: str) -> Self {              // associated fn (no self)
        Self { name = name, score = 0 }
    }
    fn won(self) -> bool {                   // method (takes self)
        self.score >= Self::MAX
    }
}

let p = Player::new("ada"); // `::` reaches associated items
p.won();                    // `.` calls a method (= Player::won(p))

struct Vec2(float, float)   // tuple struct: positional fields, indexed
let v = Vec2(1.0, 2.0);
let vx = v.0;
struct Marker;              // field-free marker type

Enums

See Enums.

enum Shape {
    Circle(float),         // tuple variant
    Rect(float, float),
    Labeled { text: str }, // struct variant
    Empty,                 // payload-free
}

let c = Shape::Circle(1.0);
let e = Shape::Empty;

impl Shape {
    fn area(self) -> float {
        match self {            // every variant must be covered
            Shape::Circle(r) => 3.14159 * r * r,
            Shape::Rect(w, h) => w * h,
            _ => 0.0,
        }
    }
}

Pacts

See Pacts.

A pact is mimas’s trait analogue: a named set of method, associated-function, and constant signatures a type satisfies with impl Pact for Type. It’s how you abstract over types without generics.

pact Draw {
    fn draw(self);
    fn describe() { print("a shape"); } // items may ship a default
}

struct Square;
impl Draw for Square {
    fn draw(self) {}                    // `describe` is inherited
}

fn render_all(items: [Draw]) { // a pact stands in anywhere a type is expected
    for item in items { item.draw(); }
}

struct Widget {
    item: Named + Identified, // require several pacts at once with `+`
}

Modules & scripts

See Modules and Scripts.

A plain file is a script — it runs top to bottom, no main required. A file that opens with module is a library: it organizes items and can’t run top-level code.

// colors.mim
module @; // name the module after the file (or: `module graphics::colors;`)

const INTERNAL = 0;        // private by default
pub const RED = "#ff0000"; // `pub` exposes it across modules
pub fn mix(a: str, b: str) -> str { a }
// main.mim — a script
use colors;               // reach items via colors::RED
use colors::{ RED, mix }; // or pull names in directly (also `colors::*`)

print(mix(RED, "#00ff00"));

Extending with Rust

See Extension with Rust.

mimas is built to embed: share Rust types and functions with the #[mimas] macro and they’re type-checked like native ones.

#[mimas]
struct User(String);

#[mimas]
impl User {
    fn greet(self) { println!("Hello, {}!", self.0); }
}
let user = User("mimas");
user.greet(); // -> Hello, mimas!