Arc Language Tour
A complete walkthrough of every Arc feature with examples.
Variables & Bindings
let x = 42 # immutable
let mut count = 0 # mutable
# Destructuring
let {name, age} = getUser() # map destructuring
let [first, second] = items # list destructuringImmutable by default. Use mut when you need to reassign.
Functions
Expression Body
For single-expression functions — no braces, no return:
fn add(a, b) => a + b
fn square(x) => x * x
fn greet(name) => "Hello, {name}!"Block Body
For multi-statement functions — last expression is the return value:
fn process(data) {
let cleaned = data |> trim |> lowercase
let parsed = parse(cleaned)
parsed # implicit return
}Closures / Lambdas
let double = x => x * 2
let add = (a, b) => a + b
numbers |> map(x => x * 2)Pattern Matching
Arc's most powerful feature. Replaces if/else chains, switch statements, and type checks:
# Value matching
match n {
0 => "zero",
1 | 2 => "small",
n if n < 0 => "negative",
n => "other: {n}"
}
# Wildcard / binding
match items {
[] => "empty",
[x] => "single: {x}",
_ => "multiple items"
}Pipeline Operator
Read left-to-right instead of inside-out:
# Without pipeline (nested)
print(join(sort(filter(words, w => len(w) > 3)), ", "))
# With pipeline (linear)
words
|> filter(w => len(w) > 3)
|> sort
|> join(", ")
|> printPipelines pass the left value as the first argument to the right function.
Collections
Lists
let nums = [1, 2, 3, 4, 5]
let first = nums[0]
let combined = [1, 2] ++ [3, 4] # concat with ++Maps
let user = {name: "Alice", age: 30}
let name = user.name # dot access
let age = user["age"] # bracket access
let shorthand = {name, age} # same as {name: name, age: age}Ranges
let r = 1..10 # 1 to 9
let digits = 0..10 # 0 to 9Comprehensions
let evens = [x * 2 for x in 1..10]
let squares = [x * x for x in 1..10 if x % 2 == 0]String Interpolation
Expressions inside {} in strings:
let name = "Arc"
let msg = "Hello, {name}!"
let math = "2 + 3 = {2 + 3}" # expressions work
let info = "length: {len(items)}" # function calls
let first = "first item: {items[0]}" # indexing
let nested = "User: {user.name} ({user.age})"Full expressions are supported inside {} — variables, math, function calls, property access, indexing.
Use \{ to escape braces in strings: "literal \{braces\}"
Type System
Arc's type system is declaration-based, not annotation-based — by design. Instead of adding types to every function signature (more tokens, more ceremony), you declare meaningful types once and let the checker do the work.
Primitive Types
Number, String, Bool, Nil, Any
Type Declarations
# Give meaning to primitives
type Email = String matching /^[^@]+@[^@]+$/
type Age = Number where x >= 0 and x <= 150
type Positive = Number where x > 0
type Username = String matching /^[a-zA-Z_]\w{2,19}$/
# Composite types
type User = {name: String, age: Age, email: Email}
# Union types
type Status = "active" | "inactive" | "banned"Constrained Types
The where and matching keywords let you encode validation directly into types:
type Positive = Number where x > 0 # runtime constraint
type Email = String matching /\S+@\S+/ # regex validationThis is more powerful than simple type annotations — your types carry meaning, not just structure.
Type Checking
arc check file.arc # Static type checkingWhy No Inline Annotations?
Arc is designed for token efficiency. Inline annotations like fn add(x: Number, y: Number): Number add tokens to every function — that's cost when AI agents write and read your code. Instead, declare a type once, use it everywhere. Less ceremony, same safety.
Tool Calls
First-class API and tool integration with @:
# HTTP methods
let user = @GET "api/users/{id}"
@POST "api/users" {name: "Arc", role: "agent"}
@PUT "api/users/{id}" updated_user
@DELETE "api/users/{id}"
# Custom tools
let answer = @llm("Summarize: {text}")
let files = @shell("ls -la")No imports, no client setup, no serialization. Just call it.
Error Handling
Arc uses map-based results for error handling:
fn safe_divide(a, b) {
if b == 0 { {ok: false, error: "division by zero"} }
else { {ok: true, value: a / b} }
}
let result = safe_divide(10, 3)
if result.ok {
print("Result: {result.value}")
} else {
print("Error: {result.error}")
}Async / Concurrency
Auto-Await
Most async calls are auto-awaited — no ceremony:
let user = fetchUser(id) # auto-awaitedParallel Fetch
Fetch multiple resources concurrently:
let [users, posts, stats] = fetch [
@GET "api/users",
@GET "api/posts",
@GET "api/stats"
]Explicit Async
let task = async { heavyComputation() }
let result = await taskControl Flow
If / El (Expression)
let label = if count > 0 { "active" } else { "empty" }
if ready {
go()
} else {
wait()
}For Loops
for item in items { process(item) }
for i in 0..10 { print(i) }
for user in users { print("{user.name}: {user.age}") }Do Loops
do {
let input = readline()
} until input == "quit"Modules
use std/io
use std/http: GET, POST
use mylib/utils: *use with / paths and : for selective imports. pub marks public exports:
pub fn greet(name) => "Hello, {name}!"Next: See the Standard Library Tutorial for hands-on examples, or the Standard Library Reference for full API docs.