Mesh

Syntax Cheatsheet ​

A quick reference for Mesh syntax. For details, see the full guides linked in each section.

Basics ​

SyntaxExample
Variable bindinglet x = 42
Type annotationx :: Int
String interpolation"Hello, #{name}!" or "Hello, ${name}!"
Heredoc string"""multiline #{expr} content"""
Comment# this is a comment
Doc comment## documents the next declaration
Module doc##! documents this module
Nested block comment#= outer #= inner =# =#
Tuple bindinglet (name, age) = ("Ada", 36)
Ignore a resultdo_work() on its own line
Multiple statementslet x = 1; let y = 2 (a newline or ; must separate them)
Printprintln("hello"), print("no newline") (a String only)
Panicpanic("message")
Default valuelet n :: Int = default()
Comparecompare(a, b) returns Less, Equal, or Greater
List preludemap(xs, f), filter(xs, f), reduce(xs, 0, f), head(xs), tail(xs)
Debug textinspect(value)

Types ​

TypeExample
Int42, 0, -5
Float3.14, 0.5
String"hello", "#{x}"
Booltrue, false
BytesBytes.from_utf8("hello")
U64, U128, I128U64.parse("18446744073709551615")
Jsonjson { status: "ok" }
Atom:ok, :not_found
Regex~r/[a-z]+/i
Unit(), nil
Tuple(1, "one"); type (Int, String)
List<T>[1, 2, 3]
Map<K, V>%{"key" => "value"}
Set<T>Set.new(), Set.from_list(["a", "b"])
RangeRange.new(0, 10) or 0..10
Queue<T>Queue.new()
Pid<M>returned by spawn(...)
Option<T>Some(42), None (shorthand: Int?, (Int, String)?)
Result<T, E>Ok(42), Err("fail") (shorthand: Int!String, (Int, Int)!String)
Fun(A) -> BFun(Int) -> String

Integer literals also support separators and radices: 1_000_000, 0xff, 0b1010, and 0o755. Floats support exponent notation such as 1.25e3; 1e3 is a Float too. A malformed or out-of-range literal (0b102, 0xffffffffffffffff, 1e999) is a compile error.

String Features ​

mesh
# Hash-brace interpolation (preferred)
let name = "World"
println("Hello, #{name}!")
println("Expr: #{count * 2 + 1}")

# Dollar-brace interpolation (also valid)
println("Hello, ${name}!")

# Heredoc strings (multiline; ordinary quotes and newlines can appear directly)
let body = """
  SELECT * FROM events WHERE id = #{id}
  """
# The newline after the opening """, the final indent-only line, and the
# closing """'s indentation are removed: body starts with "SELECT"

# Escapes: \n \t \r \0 \\ \" \$ \# \u{1F389}; any other escape is an error
let literal = "\#{not interpolated} \u{1F389}"

# JSON object literals (prefer over heredoc JSON templates)
let resp = json { status: "ok", count: n }          # {"status":"ok","count":42}
let err  = json { error: reason }                    # {"error":"not found"}
let nest = json { result: json { code: 200 } }       # {"result":{"code":200}}
HTTP.response(200, json { status: "ok", id: id })   # a Json argument is its JSON text

# Regex literals
let rx = ~r/\d+/
let rx_flags = ~r/[a-z]+/i     # i, m, s flags
let matched = Regex.is_match(rx, "hello123")

# Environment variables
let host = Env.get("HOST", "localhost")
let port = Env.get_int("PORT", 8080)

See Language Basics for details.

Functions ​

mesh
# Named function with types
fn add(a :: Int, b :: Int) -> Int do
  a + b
end

# def is a synonym for fn on named functions
def double(n :: Int) -> Int = n * 2

# Generic function and trait bound
fn identity<T>(value :: T) -> T = value
fn render<T>(value :: T) -> String where T: Display = value.to_string()

# Multi-clause (pattern matching)
fn fib(0) = 0
fn fib(1) = 1
fn fib(n) = fib(n - 1) + fib(n - 2)

# Clause parameters take any pattern; clauses may have do-end bodies
fn len([]) = 0
fn len(_ :: rest) = 1 + len(rest)
fn size_label(1 | 2) = "small"
fn size_label(_) = "large"

# Guards (a function clause's guard may be any Bool expression)
fn abs(n) when n < 0 = -n
fn abs(n) = n

# Direct self-calls in tail position are lowered to a loop
fn sum_to(n, total) do
  if n <= 0 do
    total
  else
    sum_to(n - 1, total + n)
  end
end

# Anonymous function (closure)
let double = fn(x :: Int) -> x * 2 end
let thunk = fn -> 42 end
let classify = fn 0 -> "zero" | _ -> "other" end

# Multi-line and trailing closures
let worker = fn value do
  transform(value)
end
fn with_value(value :: Int, block :: Fun(Int) -> Int) -> Int = block(value)
let result = with_value(10) do |value|
  value * 2
end
# A trailing closure is the last argument of calls, method calls, and pipes
let doubled = [1, 2, 3].map() do |x|
  x * 2
end

# Stdlib functions as methods on String, List, Map, Set, and Range values
let found = [1, 2, 3].contains(2)   # List.contains([1, 2, 3], 2)
let size = "mesh".length()          # String.length("mesh")

# Keyword arguments become one final Map argument
request("/events", method: "POST", content_type: "application/json")

# Pipe operator
let result = 5 |> double |> add_one

# Slot pipe: route value to argument position N
let result = 10 |2> add(1)   # = add(1, 10) = 11

# Multi-line pipe: trailing form (|> at end of line)
let result = 5 |>
  double |>
  add_one

# Multi-line pipe: leading form (|> at start of next line)
let result = value
  |> transform
  |> process

# Useful for long chains (e.g. HTTP router setup)
let router = HTTP.router()
  |> HTTP.on_post("/events", handle_event)
  |> HTTP.on_get("/issues", handle_issues)

Both trailing and leading forms produce identical output to their single-line equivalents -- only formatting differs. See Language Basics for details.

Patterns ​

mesh
# Wildcard and name binding
_
value

# Literal patterns
0
-1
"ok"
:ok
true
nil

# Tuple, constructors and structs
(left, right)
Some(value)
Result.Err(reason)
Point { x: 0, y }     # field `x` is 0; bind field `y`; other fields match anything

# List head/tail, or-pattern, and as-pattern
head :: tail
(0, value) | (1, value)  # alternatives must bind the same names
(x, y) as point

# Guarded match arm
case value do
  n when n > 0 -> "positive"
  _ -> "other"
end

case/match must be exhaustive; redundant arms are warned about. A when guard is any Bool expression. List-literal patterns such as [] and [a, b] match lists of exactly that length. A struct pattern (Point { x: 0, y }) matches the fields it names and binds a field named alone. Function and closure parameters take the same patterns (fn len(_ :: t) = 1 + len(t)); clauses that miss a value are a warning, and a call they miss panics.

Control Flow ​

mesh
# If/else
if x > 0 do
  "positive"
else
  "non-positive"
end

# else if chains close with one end; an if without else has type ()
if x > 0 do
  "positive"
else if x == 0 do
  "zero"
else
  "negative"
end

# Case (pattern matching)
case x do
  0 -> "zero"
  1 -> "one"
  _ -> "other"
end

# match is a synonym for case; arms can have guards
match x do
  n when n < 0 -> "negative"
  0 -> "zero"
  _ -> "positive"
end

# An arm that is only a pattern passes what it matched through
case r do
  Ok(value) # same as Ok(value) -> Ok(value)
  Err(message) -> Err(String.length(message))
end

# Several statements in an arm: -> do ... end, or an indented next line
case o do
  Some(n) -> do
    let doubled = n * 2
    doubled + 1
  end
  None ->
    let fallback = 0
    fallback
end

# For loop (list comprehension)
let doubled = for x in [1, 2, 3] do
  x * 2
end

# Filtered comprehension
let evens = for x in 0..10 when x % 2 == 0 do
  x
end

# Map iteration uses {key, value} binding syntax
for {key, value} in map do
  println("#{key}: #{value}")
end

# For with range
for i in 0..5 do
  println("#{i}")
end

# While loop
while condition do
  # body
  break
end

# continue skips to the next iteration
for x in values do
  if x < 0 do
    continue
  end
  x
end

for always produces a list. Integer ranges are end-exclusive: 0..5 yields 0 through 4. while produces Unit.

Structs & Types ​

mesh
# Struct definition
struct Point do
  x :: Int
  y :: Int
end deriving(Eq, Display)

# Struct creation
let p = Point { x: 1, y: 2 }

# Immutable struct update
let moved = %{p | x: p.x + 10}

# Generic struct
struct Box<T> do
  value :: T
end

# Sum type
type Color do
  Red
  Green
  Blue
end deriving(Eq, Display)

# Generic sum type with stored data
type Outcome<T> do
  Pending
  Complete(value :: T)
  Failed(reason :: String)
end

# Type alias (transparent -- alias and aliased type are interchangeable)
type Url = String
type Count = Int

# Generic type alias
type Pair<A, B> = (A, B)
type StringResult<T> = Result<T, String>

# Exported type alias (importable by other modules)
pub type UserId = Int

# Cross-module import of a type alias
from Types.User import UserId
fn get_user(id :: UserId) -> String do
  String.from(id)
end

Type aliases, including generic aliases, are transparent: a UserId value works wherever Int is valid with no conversion. See Type System for details.

Interfaces & Traits ​

mesh
# Define a custom interface
interface Greeter do
  fn greet(self) -> String

  # Default method
  fn label(self) -> String do
    "greeting=" <> self.greet()
  end
end

# Implement for a type
impl Greeter for Person do
  fn greet(self) -> String do
    "Hello"
  end
end

# Associated types
interface Container do
  type Item
  fn first(self) -> Self.Item
end

impl Container for MyBox do
  type Item = Int
  fn first(self) -> Int do self.value end
end

# Static method: omit self
interface Versioned do
  fn version() -> Int
end

# Generic function bound
fn show<T>(value :: T) -> String where T: Display = value.to_string()

# Deriving built-in traits
struct Tag do
  id :: Int
end deriving(Eq, Hash)

# Struct derives: Eq, Ord, Display, Debug, Hash, Json, Row, Schema
# Sum-type derives: Eq, Ord, Display, Debug, Hash, Json
# Explicit Ord must include Eq.

With no deriving clause, structs get Debug, Eq, Ord, and Hash; sum types get Debug, Eq, and Ord. An explicit clause is selective. Json, Row, and Schema must be requested explicitly; Row and Schema are struct-only.

See Type System -- Traits for details.

Numeric Traits ​

mesh
# Operator overloading via traits
impl Add for Vec2 do
  type Output = Vec2
  fn add(self, other :: Vec2) -> Vec2 do
    Vec2 { x: self.x + other.x, y: self.y + other.y }
  end
end

# Available: Add (+), Sub (-), Mul (*), Div (/), Mod (%), Neg (unary -)
let sum = v1 + v2     # calls Add.add
let neg = -v1         # calls Neg.neg

See Type System -- Numeric Traits for details.

From/Into Conversion ​

mesh
# User-defined conversion
impl From<Int> for Wrapper do
  fn from(n :: Int) -> Wrapper do
    Wrapper { value: n }
  end
end
let w = Wrapper.from(42)
let w2 :: Wrapper = 42.into()  # synthesized from From<Int> for Wrapper

# Built-in conversions
let f = Float.from(42)       # Int -> Float
let s = String.from(42)      # Int -> String

# ? operator auto-converts error types via From
fn process() -> Int!AppError do
  let n = risky()?  # String error auto-converts to AppError
  Ok(n)
end

See Type System -- From/Into for details.

Iterators ​

mesh
# Create a lazy iterator from a list, map, set, or range
let iter = Iter.from([1, 2, 3, 4, 5])

# Lazy combinators (chained with pipe operator)
Iter.from(list) |> Iter.map(fn x -> x * 2 end)
Iter.from(list) |> Iter.filter(fn x -> x > 3 end)
Iter.from(list) |> Iter.take(3)
Iter.from(list) |> Iter.skip(2)
Iter.from(list) |> Iter.enumerate()
Iter.from(a) |> Iter.zip(Iter.from(b))

# Terminal operations
Iter.from(list) |> Iter.count()
Iter.from(list) |> Iter.sum()
Iter.from(list) |> Iter.any(fn x -> x > 3 end)
Iter.from(list) |> Iter.all(fn x -> x > 0 end)
Iter.from(list) |> Iter.reduce(0, fn acc, x -> acc + x end)

# Collect into collections
Iter.from(list) |> Iter.map(fn x -> x * 2 end) |> List.collect()
Iter.from(list) |> Iter.enumerate() |> Map.collect()
Iter.from(list) |> Set.collect()
Iter.from(strings) |> String.collect()

Iter.from takes a list, a map (its (key, value) pairs), a set, or a range. for...in also takes a pipeline, binding its element type, and user-defined Iterable/Iterator values. Search with List.find(list, predicate) or Iter.find(iter, predicate), both -> Option<T>; take one element with Iter.next(iter) or iter.next(), also -> Option<T>.

See Iterators for details.

Error Handling ​

mesh
# Option type (T?)
fn find(x :: Int) -> Int? do
  if x > 0 do
    return Some(x)
  end
  None
end

# Result type (T!E)
fn divide(a :: Int, b :: Int) -> Int!String do
  if b == 0 do
    return Err("division by zero")
  end
  Ok(a / b)
end

# Early return with ?
fn compute(x :: Int) -> Int!String do
  let result = divide(x, 2)?
  Ok(result + 10)
end

# ? also propagates None from an Option-returning function
fn first_positive(values :: List<Int>) -> Int? do
  let value = List.find(values, fn n -> n > 0 end)?
  Some(value)
end

# Option and Result functions spell out common cases
fn describe(x :: Int) -> String do
  let doubled = find(x) |> Option.map(fn n -> n * 2 end)
  "#{Option.unwrap_or(doubled, 0)}"
end

# ? after a pipe applies to the call the value goes into
fn half(x :: Int) -> Int!String do
  let h = x |> divide(2) |> Result.map_err(fn e -> "half: " <> e end)?
  Ok(h)
end

# panic ends the actor (or the program from main, exit status 101)
fn port(text :: String) -> Int do
  case String.to_int(text) do
    Some(n) -> n
    None -> panic("not a port: #{text}")
  end
end

Runtime errors such as List.get out of range, Map.get of a missing key, and integer division by zero panic the same way. See Panics.

Concurrency ​

mesh
# Actor definition
actor worker() do
  receive do
    msg -> println("got: #{msg}")
    after 1000 -> println("idle")
  end

  terminate do
    println("stopping")
  end
end

# Spawn returns Pid<MessageType>; send is checked for typed Pids
let pid = spawn(worker)
send(pid, "hello")

# Actor identity and failure links (inside actor/service execution)
let me = self()
link(pid)

# Supervisor
supervisor MySup do
  strategy: one_for_one
  max_restarts: 3
  max_seconds: 5

  child w do
    start: fn -> spawn(worker) end
    restart: permanent
    shutdown: 5000
  end
end

# Service (GenServer)
service Counter do
  fn init(n :: Int) -> Int do n end
  call Get() :: Int do |s| (s, s) end
  cast Reset() do |_s| 0 end
end

let pid = Counter.start(0)
Counter.get(pid)
Counter.reset(pid)

See Concurrency for details.

Modules ​

mesh
# Standard-library modules need no import
let n = String.length("test")

# A file's path names its module: geo/shapes.mpl is Geo.Shapes
import Geo.Shapes
let p = Shapes.Point { x: 1, y: 2 }   # use the last segment

# Import specific functions
from String import length
let n = length("test")

# Parenthesized multiline selective import
from Geometry import (
  Point,
  distance,
)

# Explicit module and public declarations
pub module Geometry do
  pub struct Point do
    x :: Float
    y :: Float
  end

  pub fn origin() -> Point do
    Point { x: 0.0, y: 0.0 }
  end
end

Glob imports are not supported, and private declarations cannot be imported.

Special Function Declarations ​

mesh
# Runtime-owned clustered work; default total copy count is 2
@cluster
pub fn refresh_cache() -> Int do
  1
end

# Explicit total copy count
@cluster(3)
pub fn rebuild_index() -> Int do
  3
end

# Bodyless native ABI binding
@native("mesh_math_add")
pub fn native_add(left :: Int, right :: Int) -> Int

# C symbol for a staticlib/cdylib build (meshc build --artifact staticlib)
@export("mesh_mobile_echo")
pub fn echo(request :: Bytes) -> Bytes!String do
  Ok(request)
end

@cluster applies only to a unique public fn/def; the old clustered(work) spelling is rejected. @native declarations require explicit parameter and result types, cannot use generics/bounds/guards, and support Int, Float, Bool, String, Bytes, U64, U128, and I128 values plus Option/Result returns over supported values. @export requires a public function with exactly the signature (Bytes) -> Bytes!String; see Library Builds.

Operators ​

CategoryOperators
Arithmetic+, -, *, /, %
Comparison==, !=, <, >, <=, >=
Logicaland / &&, or / ||, not / !
Pipe|>
Slot pipe|N> (e.g. |2>)
Concatenation<>, ++ (either joins two strings or two lists)
Error propagation?
Range..

Precedence from low to high is: pipes; or; and; equality; ordering; range; concatenation; addition; multiplication; prefix; postfix call/field/?.

Int / truncates toward zero (-7 / 2 is -3) and % takes the dividend's sign (7 % -2 is 1); dividing by zero panics, and +, - and * wrap on overflow (Checked reports it instead). NaN is unequal to itself, and Float.to_int, Math.floor, Math.ceil, and Math.round saturate at the Int bounds.

Testing ​

mesh
# File: my_module.test.mpl
# Run with: meshc test my_app

test("basic assertions") do
  assert(1 + 1 == 2)
  assert_eq(10, 5 + 5)
  assert_ne(3, 4)
  assert_raises(fn() do
    assert(false)
  end)
end

describe("grouped tests") do
  setup() do
    assert(true)   # runs before each test
  end

  teardown() do
    assert(true)   # runs after each test
  end

  test("inner test") do
    assert(true)
  end
end

test("actor messaging") do
  let me = self()
  send(me, 42)
  assert_receive 42, 500
end

test("mock actor") do
  let me = self()
  let mock = Test.mock_actor(fn msg do
    send(me, "saw " <> msg)
    "ignored"
  end)
  send(mock, "ping")
  assert_receive "saw ping", 500
end

Test bodies run as actors, so self() works in them. Test.mock_actor calls its String -> String callback with each message until the test ends and ignores what it returns.

AssertionDescription
assert(expr)Fail if expr is false
assert_eq(a, b)Fail if a and b display differently
assert_ne(a, b)Fail if a and b display the same
assert_raises(fn)Fail if calling fn does not panic or fail an assertion
assert_receive pat, msFail if the next message does not match pat, or none arrives within ms (default 100)

See Testing for full guide.

Standard Library ​

mesh
# Conversions
let a = Int.to_string(42)      # "42"
let b = Float.to_string(1.5)   # "1.5"
let c = String.from(42)        # any Display value, as "#{42}" shows it
let d = Float.to_int(2.9)      # 2 (truncates; saturates at the Int bounds)

# Crypto
let input = Bytes.from_utf8("hello")
let h256 = Crypto.sha256(input)             # Bytes
let h512 = Crypto.sha512(input)             # Bytes
let h256_text = Crypto.sha256_hex(input)    # String presentation
let secret = Secret.random(32)              # Result<SecretBytes, CryptoError>
let id = Crypto.uuid4()                    # UUID v4 string

# Encoding
let raw = Bytes.from_utf8("hello")
let size = Bytes.length(raw)
let b64_bytes = Bytes.to_base64(raw)
case Bytes.to_utf8(raw) do
  Ok(s) -> println(s)
  Err(e) -> println(e)
end

# Checked wide integers
case U64.parse("18446744073709551615") do
  Ok(value) -> value |> U64.to_string() |> println()
  Err(e) -> println(e)
end

let b64 = Base64.encode("hello")
case Base64.decode(b64) do
  Ok(s) -> println(s)
  Err(e) -> println(e)
end
let url = Base64.encode_url("hello")

let hex = Hex.encode("hi")   # "6869"
case Hex.decode(hex) do
  Ok(s) -> println(s)
  Err(e) -> println(e)
end

# DateTime
let dt = DateTime.utc_now()
let iso = DateTime.to_iso8601(dt)
let ms = DateTime.to_unix_ms(dt)
case DateTime.from_iso8601("2024-01-15T10:30:00Z") do
  Ok(dt2) ->
    let next = DateTime.add(dt2, 7, :day)
    let diff = DateTime.diff(next, dt2, :day)   # Float
    let earlier = DateTime.is_before(dt2, next)  # Bool
    let later = DateTime.is_after(next, dt2)     # Bool
  Err(e) -> println(e)
end

See Standard Library for full reference.

Edit this page on GitHub
v0.1.8 Last updated: September 28, 2026