Nostos (νόστος) — a hero's homecoming
brew tap pegesund/nostos
brew install nostos
nostos --version
# For Apple Silicon (M1/M2/M3)
curl -LO https://github.com/pegesund/nostos/releases/latest/download/nostos-v0.2.1-aarch64-apple-darwin.tar.gz
tar -xzf nostos-v0.2.1-aarch64-apple-darwin.tar.gz
# For Intel Mac
curl -LO https://github.com/pegesund/nostos/releases/latest/download/nostos-v0.2.1-x86_64-apple-darwin.tar.gz
tar -xzf nostos-v0.2.1-x86_64-apple-darwin.tar.gz
# Make executable and move to PATH
chmod +x nostos
sudo mv nostos /usr/local/bin/
# If macOS blocks it, allow in System Preferences > Security & Privacy
# Or run: xattr -d com.apple.quarantine nostos
# Download and extract
curl -LO https://github.com/pegesund/nostos/releases/latest/download/nostos-v0.2.1-x86_64-unknown-linux-gnu.tar.gz
tar -xzf nostos-v0.2.1-x86_64-unknown-linux-gnu.tar.gz
# Make executable and move to PATH
chmod +x nostos
sudo mv nostos /usr/local/bin/
# Verify installation
nostos --version
# Download from GitHub Releases
# https://github.com/pegesund/nostos/releases/latest
# Or with PowerShell:
Invoke-WebRequest -Uri "https://github.com/pegesund/nostos/releases/latest/download/nostos-v0.2.1-x86_64-pc-windows-msvc.zip" -OutFile "nostos.zip"
Expand-Archive -Path "nostos.zip" -DestinationPath "."
# Add to PATH or run directly
.\nostos.exe --version
# Create hello.nos
echo 'main() = println("Hello, Nostos!")' > hello.nos
# Run it
nostos hello.nos
# Or start the interactive REPL
nostos repl
First run extracts the standard library and builds a cache (~1 second). Subsequent runs start in ~0.1 seconds.
# Pattern matching flows naturally
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n - 1) + fibonacci(n - 2)
# Lists destructure elegantly
sum([]) = 0
sum([head | tail]) = head + sum(tail)
# Quicksort in 3 lines
quicksort([]) = []
quicksort([pivot | rest]) =
quicksort(rest.filter(x => x < pivot)) ++ [pivot] ++ quicksort(rest.filter(x => x >= pivot))
# Pipes chain beautifully
result = users
.filter(u => u.active)
.map(u => u.name)
.join(", ")
Describe the shape you expect, not the steps to extract it. The compiler ensures you handle every case.
# A binary tree: either empty or a node with value and children
type Tree[T] = Empty | Node(T, Tree[T], Tree[T])
# Count nodes - pattern matching makes it trivial
size(Empty) = 0
size(Node(_, left, right)) = 1 + size(left) + size(right)
# Sum all values
sum(Empty) = 0
sum(Node(val, left, right)) = val + sum(left) + sum(right)
# Search with guards
contains(Empty, _) = false
contains(Node(val, _, _), target) when val == target = true
contains(Node(_, left, right), target) =
contains(left, target) || contains(right, target)
Every I/O operation yields automatically. No async, no await, no colored functions. Your code looks synchronous but runs concurrently.
# These HTTP requests run in parallel—no special syntax needed
fetchAll(urls) = urls.map(url => Http.get(url))
# Spawn 100,000 processes without breaking a sweat
main() = {
pids = range(1, 100001).map(i => spawn(() => worker(i)))
println("Spawned " ++ show(pids.length()) ++ " processes")
}
Use mvar for globals that multiple processes can safely read and write. No manual locks needed.
# Thread-safe global counter - all processes can safely update it
mvar requestCount: Int = 0
mvar activeUsers: List[String] = []
handleRequest(req) = {
requestCount = requestCount + 1 # Atomic increment
activeUsers = activeUsers ++ [req.user] # Safe list append
respondText(req, "Request #" ++ show(requestCount))
}
main() = {
# Each request spawns a new process, all share the mvars safely
serve(8080, handleRequest)
}
Nostos is designed to save you time. We prefer functional patterns because they're often clearer and safer, but we won't force you into contortions when a mutable variable just makes sense.
Spawn hundreds of thousands of lightweight processes. Each is ~2KB overhead. Message passing beats shared memory and locks.
Data structures use structural sharing—fast and safe. Use var when a mutable variable makes your intent clearer.
Hindley-Milner inference means you rarely write types, but the compiler catches errors at compile time. Generics and traits work seamlessly.
Register-based VM with Cranelift-powered JIT. Hot paths compile to native code.
Live reload, autocomplete, inline errors, state inspection. The REPL isn't just for experiments—it's your development cockpit.
Real-time error checking, go to definition, smart autocomplete with type inference, integrated REPL in your editor.
Query your database with minimal friction. No ORM, just plain queries and safe types.
type User = { name: String, email: String }
main() = {
conn = Pg.connect("host=localhost dbname=mydb user=postgres")
# Parameterized queries prevent SQL injection
rows = Pg.query(conn, "SELECT name, email FROM users WHERE age > $1", 18)
# Or map directly to typed records
users: List[User] = query[User](conn, "SELECT name, email FROM users", ())
users.map(u => println(u.name ++ ": " ++ u.email))
}
Connection pooling, transactions, prepared statements, pgvector, LISTEN/NOTIFY, TLS
Production-ready networking with automatic keep-alive and TLS.
# Server
handle(req) = match req.path {
"/api/users" -> jsonResponse(getUsers())
"/health" -> textResponse("OK")
_ -> notFound()
}
main() = Server.start(8080, handle, workers: 8)
# Client
response = Http.get("https://api.example.com/data")
data = Json.parse(response.body)
Define shared behavior without class hierarchies.
trait Drawable
draw(self) -> String
end
type Circle = { radius: Float }
type Square = { side: Float }
Circle: Drawable
draw(self) = "●"
end
Square: Drawable
draw(self) = "■"
end
# Works with any Drawable
render[T: Drawable](shapes: List[T]) = shapes.map(s => s.draw()).join(" ")
Organize code into modules. The compiler catches name conflicts at compile time.
# Import everything from a module
use math.*
# Import specific items
use stdlib.server.{serve, respondText}
# Import with aliases to avoid conflicts
use graphics.{draw as graphicsDraw}
use text.{draw as textDraw}
# If two modules export the same name, the compiler catches it:
# Error: import conflict: `helper` is imported from both `a` and `b`
# Help: use qualified name `b.helper` or selective import
Full-stack reactive apps. Server-side rendering, automatic DOM diffing. Change state, UI updates.
use stdlib.rweb.*
reactive Todo = { text: String, done: Bool }
reactive State = { todos: List[Todo] }
sessionSetup(writerId) = {
state = State([])
renderPage = () => RHtml(div([
h1("Todo App"),
ul(state.todos.map(t => li(t.text))),
input(type: "text", dataAction: "add")
]))
onAction = (action, params) => match action {
"add" -> { state.todos = state.todos ++ [Todo(params.text, false)] }
_ -> ()
}
(renderPage, onAction)
}
main() = startRWeb(8080, "Todos", sessionSetup)
Real-time bidirectional communication with TLS support
Full-stack reactive apps with server-side rendering
Parse, generate, and transform JSON data
Async file operations with streaming
SHA-256/512, bcrypt, secure random
Fast pattern matching, guaranteed linear time
Low-level networking when you need it
Type-safe templates with named parameters
# Clone and build
git clone https://github.com/pegesund/nostos.git
cd nostos
cargo build --release
# Run the REPL
./target/release/nostos
# Run a program
./target/release/nostos examples/hello_server.nos
# Install VS Code extension
cd editors/vscode && npm install && npm run package
code --install-extension nostos-*.vsix
Readable code is maintainable code. Pattern matching, clean syntax, and functional patterns make code self-documenting. If an LLM can read it, so can your future self.
Concurrency without complexity. No async, no await, no colored functions. Spawn a million lightweight processes. Write sequential code that runs in parallel.
Shared state without tears. Use mvar for thread-safe globals. Multiple processes read and write atomically—no manual locks, no race conditions, no headaches.
Functional by default, pragmatic when needed. Immutable data structures prevent bugs. But when a var or a while loop is clearer, use it without guilt.
Types that work for you. Hindley-Milner inference means you rarely write types. The compiler catches errors; you focus on logic.
From prototype to production. The same language for scripting, web servers, and data processing. PostgreSQL, HTTP, WebSockets—built in, not bolted on.
Built on giants. Tokio for I/O, Cranelift for JIT, Rust for reliability. We don't reinvent wheels—we assemble the best ones.
Use our comprehensive language reference as a skill for AI coding assistants like Claude, Cursor, or GitHub Copilot. One file, complete documentation.
View Language Reference