Welcome to Silo
SourceAn opening tour of what Silo is and what makes it distinctive.
Silo is Forth blended with Rust, with a dependently typed core quietly keeping you honest. The surface is concatenative — you push values onto a stack and chain small words (the Forth term for a function) into pipelines — but the type system, trait system, pattern matcher, and algebraic effects will feel familiar to anyone who's written Rust.
The dependent types are there to catch the sort of mistake that's annoying to find at runtime: off-by-one on an array, mixing seconds and metres, a time zone attached to the wrong calendar. You don't have to reach for them, but they're there, and the standard library uses them whenever it buys you a real guarantee.
If "dependently typed" makes you nervous, don't let it. You will not be writing proofs. You write ordinary code, and sometimes a function signature says (Vec a n) instead of (Vec a) to record that the length is known. That's most of it.
What's distinctive
A few guarantees Silo's type system gives you that are worth knowing up front, because they shape the shape of the standard library:
- Nothing can panic without declaring
+Panic. If a word's signature doesn't list+Panic, it cannot callpanic, cannot trigger an unwrap-on-None, cannot divide by zero without handling it. The effect is as visible as I/O. - Nothing can loop forever without declaring
+Div. Silo is total by default. Non-termination is opt-in and signature-visible. - Nothing can touch the outside world without a specific effect.
+Console,+FileSystem,+Net,+Temporal, etc. A pure word cannot surprise you by quietly reading a file. - Async and threads are effects, not a parallel dialect. The
+Concurrentand+Executoreffects compose with every other effect. A pure word can be called freely from a task, a thread, or synchronous code — there are no "function colors" forcing anasyncsplit. - Locality modes let the type system enforce resource discipline. The default mode is an ordinary shareable value;
local,unique, andlinearmodes opt into can't-escape, no-aliasing, and must-be-consumed semantics when a resource calls for them. - No lifetime system. The work that Rust does with lifetime parameters is handled here by the modes above — simpler at the surface, with most of the same soundness guarantees on the resource side.
- Dependent types track real invariants. Lengths, index ranges, unit tags — the things that normally become runtime bugs.
- Everything you can do at compile time, you can do at the REPL. Define traits, implement them, declare types, run macros, inspect
:type-info— the REPL runs the same frontend the batch compiler does. No separate "runtime" dialect with a smaller feature set.
Each of these is elaborated in its own chapter; they're listed together here because taken as a set they're what the language actually feels like to work with.
The layers underneath
Silo runs on a host. The language is deliberately sans-IO: the compiler and the abstract machine never touch the disk or the network on their own. Every side effect goes through a typed effect handler supplied by whatever is embedding Silo — usually a Rust host.
A command-line host publishes +Console, +FileSystem, +Env. A browser host might publish +Http, +DOM, +Storage. An embedded host on a microcontroller might publish +GPIO and +SPI and nothing else. The same Silo code can run under any of them, as long as the effects it declares are ones the host knows how to handle.
Reading this handbook
Chapters are cumulative: each assumes the ones before it. The order is roughly "syntax you meet in the first ten minutes" first, then the type system, then the standard library, then the features you reach for once you're building something real.
If you already know Forth, skim chapter 2 and go straight to the type system and traits. If you know Rust but have never pushed a value onto a stack, the reverse — chapter 2 is where to slow down.
A first taste
Here is a complete, typechecked Silo function. It sums the squares of the integers from 1 up to n:
:fn sum-of-squares ( Int -> Int )
pop-> n
0 .. n 0 [ pop-> acc pop-> i
i 1 + pop-> k
acc k k * +
] .fold
:end
A few things worth noticing even before any explanation:
( Int -> Int )is the stack effect. It plays the role of a type signature: this word pops oneIntand pushes oneInt.pop->binds the top of the stack to a name. Silo is stack-based, but you rarely juggle values — you name whatever you want to reuse. There is alsopeek->, which copies instead of popping.- There is no
returnkeyword. Whatever is on the stack when the word finishes is the return value — that's the normal case. If you need to bail out from a nested block,:retexits the enclosing word immediately with whatever's on the stack at that point. [ ... ]is a quotation — a first-class block of code with captured bindings. If you know Rust, think of it as a closure; if you know ML or Haskell, a lambda..foldis an ordinary method that takes one of these.
Traits are how you abstract
Most of Silo's machinery for polymorphism goes through traits, just like Rust. Arithmetic operators are trait methods (+ calls .add, = calls .eq, and so on). Iteration, formatting, comparison, hashing, conversion — all traits. There is no orphan rule: any package can implement any trait on any type, with imports controlling which impls are in scope.
You will see traits introduced in chapter 9 and reappearing in almost every chapter after it.
What Silo is for
Silo is designed for the kind of code where cutting a class of bugs early saves a great deal of work later: long-running services, simulations, anything that touches a calendar or a locale, anything with non-trivial concurrency. The dependent types give you index-safe collections and unit-safe arithmetic. The effect system makes it easy to be precise about what code actually needs the filesystem or the network or the current time.
Silo is not a scripting language and it is not a toy. It is a serious attempt to let types and effects carry real structural weight without making the everyday code ceremonial.
Before you continue
You do not need to have installed anything to read this handbook. Every chapter has complete, typechecked examples you can read as you go. When you're ready to run code, the project README.md at the repo root describes how to build the CLI and the REPL.
The rest of the book assumes you've met the stack and the word. Chapter 2 makes that introduction.