Grasm v1.0.0 Get started

Reference

Syntax

The whole language on one page. Every form below is checked by a conformance program in spec/conformance, so an example that stopped compiling would fail the build rather than sit here misleading somebody.

Declarations

fnA function. The row after / is inferred and checked.
fn add(a: i64, b: i64) -> i64 {
    a + b
}

fn greet(name: str) / {io} {
    print("hello, {name}")
}
pubVisible outside the module. Types are always visible.
pub fn parse(text: str) -> i64 {
    text.parse_int() ?? 0
}
let / varA binding. let cannot be reassigned.
let limit = 10
var seen = 0
seen = seen + 1
type — structNamed fields. At least one.
type Point = {
    x: f64,
    y: f64,
}
type — sumAlternatives, each with named payload fields.
type Shape =
    | Circle(r: f64)
    | Rect(w: f64, h: f64)
    | Empty
type — aliasAnother name for a type.
type Meters = f64
effectAn interface a handler supplies at the call site.
effect Clock {
    fn now() -> i64
}
interface / implA contract and its implementation.
interface Show {
    fn show(self) -> str
}
importA module or package. Named by its last path segment.
import json
import crypto_hash
constA module-level value, one for the whole program.
pub let MAX_RETRIES = 5
testA unit test, or a property test when it takes parameters.
test "reversing twice is the identity" (xs: [i64]) {
    assert(xs.rev().rev() == xs)
}
extern fnA C symbol. Tier 3 — every call sits in unsafe.
extern fn getpid() -> i64
extern fn strlen(s: str) -> i64

Types

PrimitivesOne integer type, one float, and text is UTF-8.
let a: i64 = 42
let b: f64 = 1.5
let c: bool = true
let d: str = "text"
bytesA sequence of 0..255. Distinct from [i64] on purpose, and writable in place at one byte each rather than eight.
let raw: bytes = bytes_of([72, 105])
let from_text = "hi".to_bytes()

var buf = bytes_filled(4, 0)
buf.set(0, 255)
buf.fill(7)
buf.copy_from(2, raw)
ListsHomogeneous and growable.
let xs: [i64] = [1, 2, 3]
let empty: [str] = []
MapsKeyed, with get returning an Option.
var m: Map[str, i64] = map()
m.set("k", 1)
let v = m.get("k") ?? 0
TuplesPositional, reached with .0.
let pair = (1, "one")
let first = pair.0
OptionSome or None.
let found: Option[i64] = Some(3)
let n = found ?? 0
Error unionsT ! E — a value or a failure.
fn half(n: i64) -> i64 ! MathError {
    if n % 2 != 0 {
        fail(NotEven(n))
    }
    n / 2
}
Function typesfn(...) closes up; it is a type, not a call.
fn apply(f: fn(i64) -> i64, v: i64) -> i64 {
    f(v)
}
GenericsSquare brackets, on the declaration.
fn first_of[T](xs: [T]) -> Option[T] {
    if xs.len() == 0 {
        return None
    }
    Some(xs[0])
}
ref TTier 2 — a generational reference, storable in a struct.
let r = ref(Point{ x: 1.0, y: 2.0 })
let alive_now = alive(r)
release(r)

Expressions

Operatorsand, or, not are words. ^ is power.
let a = 1 + 2 * 3 - 4 / 2 % 3
let b = 2 ^ 8
let c = a > 0 and not (b < 0)
ComparisonStructural, on everything comparable.
let same = [1, 2] == [1, 2]
ifAn expression: it yields a value.
let label = if n > 0 {
    "positive"
} else {
    "not"
}
matchExhaustive. Arms are newline-separated.
match shape {
    Circle(r) -> 3.14159 * r ^ 2
    Rect(w, h) -> w * h
    Empty -> 0.0
}
PatternsLiterals, wildcards, alternatives, and tuples.
match n {
    0 -> "zero"
    1 | 2 | 3 -> "small"
    _ -> "large"
}
Closures|params| body. The body may be a block.
let double = |n| n * 2
let total = xs.fold(0, |acc, v| acc + v)
Interpolation{} inside a string. A literal brace is \{}.
print("{name} has {xs.len()} item(s)")
tryA prefix. Propagates the error branch.
fn load(path: str) -> str ! IoError / {io} {
    try read_file(path)
}
??The else of an Option or a fallible call. Lazy.
let port = env("PORT") ?? "8080"
Pipeline|> feeds the left into the right.
let n = xs |> len
Ranges.. excludes the end, ..= includes it.
for i in 0..10 {
    print("{i}")
}
BlocksA block is an expression; its last line is its value.
let v = {
    let a = 2
    a * a
}

Statements and control

forOver a list, a map or a range.
for x in xs {
    print("{x}")
}
whileWhile the condition holds.
while queue.len() > 0 {
    let _v = queue.pop()
}
break / continueLeave or skip an iteration.
for x in xs {
    if x < 0 {
        continue
    }
    if x > 99 {
        break
    }
}
returnLeaves the function early.
fn find(xs: [i64], want: i64) -> bool {
    for x in xs {
        if x == want {
            return true
        }
    }
    false
}
failTakes the error branch of a T ! E.
fail(NotEven(n))
Assignment=, and the compound forms.
var n = 0
n = 5
n += 1
n -= 1
n *= 2
n /= 2

Ownership and effects

Parameter modesDefault reads; inout mutates; own takes.
fn read(xs: [i64]) -> i64 { xs.len() }
fn grow(inout xs: [i64]) { xs.push(1) }
fn consume(own xs: [i64]) -> i64 { xs.len() }
Effect rowsWritten after /. Inferred, then checked.
fn fetch(url: str) -> str ! IoError / {io, net} {
    try http_get(url)
}
with / handled_bySupplies a handler for an effect, for one scope.
with Clock handled_by ClockHandler{ now: || 1000 } {
    print("{Clock.now()}")
}
regionTier 1 — nothing allocated inside may outlive it.
region {
    var scratch: [i64] = []
    scratch.push(1)
}
unsafeTier 3 — where the guarantees stop, named in the source.
unsafe {
    let pid = getpid()
}
parallel / spawnStructured concurrency: the block waits for its tasks.
parallel {
    spawn {
        print("one")
    }
    spawn {
        print("two")
    }
}

Lexical

Comments// to end of line; /* */ nests; /// documents.
// a line
/* a block /* that nests */ */
/// documentation for what follows
fn documented() -> i64 { 1 }
NumbersDecimal, hex, underscores, floats and exponents.
let a = 1_000_000
let b = 0xFF
let c = 1.5
let d = 1e-9
StringsDouble-quoted, with escapes; """ for raw.
let s = "a\tb\n"
let raw = """no escapes here"""
Statement endsA newline ends a statement. A line may continue if it ends with an operator.
let total = 1 +
    2 +
    3
IdentifiersLetters, digits and _; not starting with a digit.
let snake_case = 1
let _ignored = 2

One namespace for types. Every type is reachable from every module, which is why pub means nothing on one — and why two packages cannot both declare a Node. A generator checks the whole package set for that collision on every build.

The language reference gives the rules behind these forms; the specification gives the clause and the conformance program for each.