Skip to content

Arc Language Tour ​

A complete walkthrough of every Arc feature with examples.

Variables & Bindings ​

arc
let x = 42                      # immutable
let mut count = 0                # mutable

# Destructuring
let {name, age} = getUser()      # map destructuring
let [first, second] = items      # list destructuring

Immutable by default. Use mut when you need to reassign.

Functions ​

Expression Body ​

For single-expression functions — no braces, no return:

arc
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:

arc
fn process(data) {
  let cleaned = data |> trim |> lowercase
  let parsed = parse(cleaned)
  parsed  # implicit return
}

Closures / Lambdas ​

arc
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:

arc
# 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:

arc
# Without pipeline (nested)
print(join(sort(filter(words, w => len(w) > 3)), ", "))

# With pipeline (linear)
words
  |> filter(w => len(w) > 3)
  |> sort
  |> join(", ")
  |> print

Pipelines pass the left value as the first argument to the right function.

Collections ​

Lists ​

arc
let nums = [1, 2, 3, 4, 5]
let first = nums[0]
let combined = [1, 2] ++ [3, 4]    # concat with ++

Maps ​

arc
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 ​

arc
let r = 1..10                        # 1 to 9
let digits = 0..10                   # 0 to 9

Comprehensions ​

arc
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:

arc
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 ​

arc
# 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:

arc
type Positive = Number where x > 0       # runtime constraint
type Email = String matching /\S+@\S+/    # regex validation

This is more powerful than simple type annotations — your types carry meaning, not just structure.

Type Checking ​

bash
arc check file.arc    # Static type checking

Why 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 @:

arc
# 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:

arc
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:

arc
let user = fetchUser(id)   # auto-awaited

Parallel Fetch ​

Fetch multiple resources concurrently:

arc
let [users, posts, stats] = fetch [
  @GET "api/users",
  @GET "api/posts",
  @GET "api/stats"
]

Explicit Async ​

arc
let task = async { heavyComputation() }
let result = await task

Control Flow ​

If / El (Expression) ​

arc
let label = if count > 0 { "active" } else { "empty" }

if ready {
  go()
} else {
  wait()
}

For Loops ​

arc
for item in items { process(item) }
for i in 0..10 { print(i) }
for user in users { print("{user.name}: {user.age}") }

Do Loops ​

arc
do {
  let input = readline()
} until input == "quit"

Modules ​

arc
use std/io
use std/http: GET, POST
use mylib/utils: *

use with / paths and : for selective imports. pub marks public exports:

arc
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.

A programming language designed by AI agents, for AI agents.