Syntax tour

Gossamer's surface is Rust with two simplifications:

  • No lifetime annotations. References express aliasing intent; the GC owns the memory.
  • Semicolons are optional at statement boundaries.

See the full grammar in grammar/ once it is committed.

Items

const PI: f64 = 3.14159
static MAX: u32 = 1024

struct Point { x: f64, y: f64 }
struct Pair(i64, i64)

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

trait Area {
    fn area(&self) -> f64;
}

impl Area for Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => 3.14159 * r * r,
            Shape::Rect { w, h } => w * h,
        }
    }
}

Expressions

Everything is an expression. Blocks evaluate to their tail:

let max = if x > y { x } else { y }
let label = match status {
    200 => "ok",
    404 => "missing",
    _ => "other",
}

Forward pipe (|>)

The forward-pipe operator threads a value through a chain of calls. x |> f desugars to f(x); x |> f(a, b) to f(a, b, x) — the piped value lands in the last positional slot. Methods work the same way: x |> recv.m(a) becomes recv.m(a, x). |> is left-associative with very low precedence, so a |> f |> g reads as g(f(a)) with no parentheses needed:

fn double(x: i64) -> i64 { x * 2 }
fn add(a: i64, b: i64) -> i64 { a + b }
fn clamp(lo: i64, hi: i64, x: i64) -> i64 {
    if x < lo { lo } else if x > hi { hi } else { x }
}

// Reads left-to-right instead of inside-out.
let n = 3i64 |> double |> add(10i64) |> clamp(0i64, 100i64)

// Equivalent nested form:
let same = clamp(0i64, 100i64, add(10i64, double(3i64)))

Pattern matching

  • _ — wildcard.
  • name / mut name — bind.
  • Some(inner) / None — variant destructure.
  • Point { x, y } — struct destructure.
  • (a, b) — tuple destructure.
  • 1..=5 — range.
  • a | b — or-pattern.
  • x @ 1..=3@-binding.
  • .. — rest.

Guards: Some(n) if n > 0 => ...

Loops

loop { ... break value }
while cond { ... }
for item in iter { ... }

break value returns a value from loop. continue jumps to the top.

Error handling

fn load(path: String) -> Result<String, io::Error> {
    let raw = os::read_file_to_string(&path)?
    Ok(raw)
}

? propagates the Err variant. Wrap with std::errors::wrap(err, "while loading config") for context.

Concurrency

let (tx, rx) = channel::<i64>()
go fn() { tx.send(42) }()
let n = rx.recv()

select {
    a = rx_a.recv() => handle_a(a),
    b = rx_b.recv() => handle_b(b),
    _ = time::after(5000) => timeout(),
}

go expr spawns a goroutine. Channels are typed and bounded; select multiplexes receives.

Closures and higher-order fns

Lambdas use |param: T| body; captures from the enclosing scope work transparently (GC-managed, no move).

Higher-order parameters distinguish two callable types:

Type Accepts Representation
fn(args) -> ret non-capturing items only raw code pointer
Fn(args) -> ret bare items and capturing closures env+code fat pointer
fn apply(f: Fn(i64) -> i64, x: i64) -> i64 { f(x) }

fn main() {
    let scale = 10i64
    let scaled = |y: i64| scale * y    // captures `scale`
    println!("{}", apply(scaled, 5))   // 50

    fn add_one(y: i64) -> i64 { y + 1 }
    println!("{}", apply(add_one, 41)) // 42 — bare fn coerces
}

The conversion at the call boundary is implicit. Single trait variant — FnMut / FnOnce parse but lower to the same Fn(_) shape (the borrow-style split Rust draws is unnecessary in a fully GC'd world).

Attributes

#[test]
fn add_adds() { ... }

#[bench]
fn bench_hot_path() { ... }

#[lint(allow(unused_variable))]
fn scratch() { let x = 1i64 }

Modules

use std::http
use std::http::{Handler, Request, Response}
use example.org/other::widget

A project's module tree is file-based: src/foo.gos becomes mod foo, src/bar/mod.gos becomes mod bar.

Numeric literals

  • 42 — plain int, inferred type.
  • 42i32 / 42u64 — explicit width.
  • 0xff / 0b1010 / 0o777 — bases.
  • 1_000_000 — underscore separator.
  • 1.0f32 / 1.0f64 — float.

String literals

  • "hello" — ordinary double-quoted string. Spans multiple lines without extra syntax; embedded newlines are preserved.
  • "\n" / "\t" / "\\" / "\"" — standard escapes.
  • r"raw" / r#"with embedded "quotes""# — raw strings.
  • b"bytes" / b'c' — byte literals for binary protocols.

Formatted output

Gossamer has no macro system and no ! syntax. Formatted output goes through plain variadic builtins:

let name = "jane"
let age = 30
println("hello, ", name, "! you are ", age, " years old.")
let greeting = format("welcome, ", name)

Every builtin below stringifies each argument and joins them with a single space:

Builtin Effect
format(a, b, …) Returns a String.
println(a, b, …) Writes to stdout + newline.
print(a, b, …) Writes to stdout, no newline.
eprintln(a, b, …) Writes to stderr + newline.
eprint(a, b, …) Writes to stderr, no newline.
panic(a, b, …) Unwinds with the rendered message.

For the single-String output shape, + concatenates without adding a separator:

let greeting = "hello, ".to_string() + &name

Writing name!(…) is a hard parse error — the ! suffix is reserved for no purpose today.