Surreal
A programming language with Rust-like syntax and Erlang-style concurrency.
Surreal brings Rust's expressive type system and familiar syntax to the BEAM virtual machine. Write concurrent, fault-tolerant applications with the ergonomics of Rust and the battle-tested runtime of Erlang/OTP.
use send;
Why Surreal?
Surreal combines the best of two worlds:
| From Rust | From Erlang/OTP |
|---|---|
struct, enum, impl blocks |
Lightweight processes |
trait definitions and bounds |
Message passing |
| Generics with type parameters | Preemptive scheduling |
| Pattern matching with guards | Hot code reloading |
Result<T, E> and ? operator |
Fault tolerance |
Module system with mod/use |
Supervisor trees |
| Derive macros | Hex package ecosystem |
Quick Start
# Create a new project
# Build the project
# Run the project
# Run tests
# Start the REPL
Compilation
Source → Lexer → Parser → AST → Core Erlang → BEAM
Surreal compiles to Core Erlang, which is then compiled to BEAM bytecode. This gives you full access to the Erlang/OTP ecosystem, including OTP behaviors, Hex packages, and interop with Erlang and Elixir libraries.
Language Features
Concurrency
use send;
- Processes: lightweight, isolated units of execution
- Message Passing: async send, receive with timeout, selective receive
- Links: bidirectional crash notification between processes
- Monitors: one-way crash notification (DOWN messages)
- Process Registry: register/unregister/whereis for named processes
Data Types
// Primitives
let i: int = 42; // Arbitrary precision integer
let f: float = 3.14; // 64-bit float
let b: bool = true; // Boolean
let a: Atom = :ok; // Atom (interned string)
// Strings and Binaries
let s: String = "hello"; // UTF-8 string (list of codepoints)
let bin: Binary = "hello"; // Binary (byte sequence)
// Collections
let list: = ; // Linked list
let tuple: = ; // Fixed-size tuple
let map: Map = ; // Hash map
// Process types
let pid: Pid = self; // Process identifier
let r: Ref = make_ref; // Unique reference
// Generic types
let opt: = Some; // Optional value
let res: = Ok; // Result type
// Any type (dynamic)
let x: any = "anything"; // Accepts any value
Type Aliases
type UserId = int;
type Handler = fn ;
type Cache<T> = Map; // Generic type alias
Control Flow
// If expressions
let status = if count > 0 else ;
// If-else chains
let grade = if score >= 90 else if score >= 80 else if score >= 70 else ;
// For loops with ranges
for i <- 1..10
// For loops with lists
for item <- items
// While loops (via recursion)
Pattern Matching
// Match expressions
// Destructuring in let bindings
let = point;
let = items;
let User = user;
// Pattern matching in function arguments
Error Handling
Structs and Enums
Traits
Generics
// Generic functions
// Generic structs
// Trait bounds
Derive Macros
use Serialize;
use Deserialize;
// Now you can serialize/deserialize:
let json = to_string;
let user: User = from_str_typed;
Attributes
OTP Integration
Implement OTP behaviors using traits:
use Application;
Closures
// Anonymous functions with closure capture
let multiplier = 2;
let double = ;
// Multi-argument closures
let add = ;
// Closures as arguments
let numbers = ;
let doubled = numbers |> map;
let evens = numbers |> filter;
List Comprehensions
// Basic comprehension
let squares = for x <- ;
// With filter
let even_squares = for x <- 1..10, x % 2 == 0 ;
// Nested comprehension
let pairs = for x <- , y <- ;
// With pattern matching
let names = for <- users ;
Receive with Timeout
// Wait for message with timeout
receive after 5000
// Selective receive
Links and Monitors
// Bidirectional link - if either process crashes, both are notified
let pid = spawn_link;
// One-way monitor - only the caller receives DOWN message
let ref = monitor;
receive
// Unlink/demonitor
unlink;
demonitor;
Pipe Operator
let result = data
|> transform
|> filter
|> map;
Binaries
// Binary literals
let bin: Binary = "hello";
// Binary pattern matching
// Binary construction
let packet = payload":binary>>;
Erlang Interop
Call Erlang/Elixir functions directly:
// Call Erlang module
let result = :reverse;
// Call Elixir module
let json = :"Elixir.Jason"encode;
Project Structure
A Surreal project looks like this:
my_app/
├── surreal.toml # Project configuration
├── src/
│ ├── lib.surreal # Root module
│ ├── app.surreal # Application entry point
│ └── handlers/
│ ├── mod.surreal # Submodule declarations
│ └── api.surreal # Handler implementation
└── _build/ # Build artifacts
surreal.toml
[]
= "my_app"
= "0.1.0"
[]
= "my_app::app" # OTP application module
[]
= "2.12.0" # Hex packages
= "1.4.4"
[]
= []
= ["json"]
Module System
Surreal uses a Rust-like module system:
// src/lib.surreal - root module
// Loads src/app.surreal
// Loads src/handlers/mod.surreal
// src/handlers/mod.surreal
// Loads src/handlers/api.surreal
CLI Commands
| Command | Description |
|---|---|
surreal new <name> |
Create a new project |
surreal build |
Build the project |
surreal run |
Build and run |
surreal test |
Run tests |
surreal test "pattern" |
Run tests matching pattern |
surreal shell |
Interactive REPL |
surreal deps get |
Fetch dependencies |
surreal deps update |
Update dependencies |
surreal bindgen |
Generate type stubs from Erlang |
Build Options
Building from Source
# Clone the repository
# Build the compiler
# Run tests
# Install locally
Examples
See the examples/ directory for complete examples:
http_api/- JSON API server using Cowboyconcurrency/- Process spawning and message passinggenserver/- OTP GenServer implementation
Related Projects
- tree-sitter-surreal - Tree-sitter grammar for editor support
License
MIT