-
Trustworthy (Stability, Security, Correctness)
-
Ergonomic (Concise, Expressive)
-
Performant (Solid Execution Speed, Efficient Resource Usage)
Why build Gossamer? Why use it?
I enjoy building web services and command line tools. I always have another idea I want to explore, another service I want to deploy, or another manual task I want to automate with a script.
I love the confidence that comes from Rust and F#: the feeling that if it compiles, it probably works. Algebraic data types, pattern matching, and explicit error handling feel like a natural way to build correct and maintainable software.
I also love having a REPL open or being able to iterate quickly on a script without waiting for a compile step.
Go, meanwhile, is an incredible tool for building and shipping software. It feels fast, minimal, and frictionless: a garbage-collected language with built-in concurrency and an extensive standard library.
What if one language could combine all of those ideas?
What if I could iterate quickly in a REPL or script, then compile the exact same program into an optimized standalone binary with no code changes?
What if that language could perform Python like while interpreted, but closer to Go when compiled?
I built Gossamer because I wanted that language for myself.
My goal is for Gossamer to replace Rust, Go, F#, Kotlin, and Python for most of my own projects and use cases.
| Feature | Rust | Go | F# | Python | Elixir |
|---|---|---|---|---|---|
| Strong static type system | ✓ | ✓ | ✓ | ||
| Algebraic data types / discriminated unions | ✓ | ✓ | ✓ | ||
| Exhaustive pattern matching | ✓ | ✓ | ✓ | ||
Error handling via ? with Result & Option |
✓ | ✓ | |||
No null by default |
✓ | ✓ | ✓ | ||
| Immutable by default | ✓ | ✓ | ✓ | ||
| (Local) Borrow checking | ✓ | ||||
| Automatic memory management | ✓ | ✓ | ✓ | ✓ | |
| Lightweight concurrency primitives | ✓ | ✓ | ✓ | ||
| Fast compilation | ✓ | ||||
| Small portable binaries | ✓ | ✓ | ✓ | ||
Pipe operator (|>) |
✓ | ✓ | |||
| Interpreted / scripting mode | ✓ | ✓ | ✓ | ||
| Interactive REPL | ✓ | ✓ | ✓ |
Gossamer's automatic memory management is deterministic: reference counting reclaims a value the moment its last reference dies, an automatic cycle collector handles reference cycles, and there is no tracing collector. It is closely modeled on Swift's ARC, with the addition that reference cycles are reclaimed for you instead of having to be broken by hand.
Plus arena { } blocks, inspired by Zig: everything allocated inside
the block is bump-allocated and freed wholesale when the block exits -
pointer-bump allocation, O(slabs) reclamation, and headerless 16-byte
nodes for small enums. See the
memory model chapter.
Not Transpiled
Gossamer compiles directly to native, it does not transpile to Rust or Go.
No Macros
No user-defined macros. Metaprogramming is Zig-style comptime: code
runs during compilation and folds into the program, and a for loop
over typeInfo::<T>() reflection generates native per-field code.
Gossamer is Extensible in Rust.
Gossamer is built to extend simply via (synchronous) Rust.
- Language spec:
SPEC.md - Project style guide:
GUIDELINES.md - AI skill card:
SKILL.md- drop this file into a model's context to teach it how to write idiomatic Gossamer (also embedded ingos skill-prompt). - Editor integrations:
danpozmanter/gossamer-editor-support(VSCode, Vim, Neovim, Helix, Emacs, Sublime, Zed, plus a tree-sitter grammar) - Contributing:
CONTRIBUTING.md
Source files use the .gos extension.
The CLI is gos.
Manifests live in project.toml.
Pre-stable. A formal compatibility policy will land with the first stable tag; until then, treat the public API as may-change-with-notice.
For scripts and examples, the entry file may skip the fn main wrapper:
bare statements at file scope become the body of an implicit fn main(),
so this is a complete program:
println!("Hello World")
A top-level ? makes the implicit main return Result<(), errors::Error>; set a process exit code with std::process::exit(n).
Gossamer leans on a forward-pipe operator (|>) so data flows
left-to-right. x |> f(a, b) desugars to
f(a, b, x), and |> chains cleanly with methods, closures, and
plain functions:
use std::{iter, strings}
fn double(x: i64) -> i64 { x * 2 }
fn add(a: i64, b: i64) -> i64 { a + b }
fn clamp(lo: i64, hi: i64, x: i64) -> i64 {
if x < lo { lo } else if x > hi { hi } else { x }
}
fn main() {
// 3 -> double -> add 10 -> clamp to [0, 100]
let n = 3 |> double |> add(10) |> clamp(0, 100)
println!("arithmetic: {}", n)
// Free functions pipe the same way.
let words = " Hello World "
|> strings::to_lowercase
|> strings::split_whitespace
|> iter::count
println!("words: {}", words)
}
Types define their own operators. impl Add for T gives + its
meaning, and the same shape covers -, *, [], and the rest.
Structural == and .clone() are automatic - no derive needed - so a
custom operator is the part that is genuinely yours to write:
struct Vec2 { x: f64, y: f64 }
impl Add for Vec2 {
fn add(self, o: Vec2) -> Vec2 { Vec2 { x: self.x + o.x, y: self.y + o.y } }
}
fn main() {
let sum = Vec2 { x: 1.5, y: 2.0 } + Vec2 { x: 3.0, y: 4.0 }
println!("({}, {})", sum.x, sum.y) // (4.5, 6)
println!("{}", sum == sum.clone()) // true
}
A goroutine + channel example:
use std::sync::channel
fn add(a: i64, b: i64) -> i64 { a + b }
fn main() {
let (tx, rx) = channel::<i64>()
go fn() { tx.send(40 |> add(2)) }()
if let Some(answer) = rx.recv() {
println!("answer: {}", answer)
}
}
Or spawn a goroutine and join its result - Ok(value), or Err(message)
if it panicked:
fn add(a: i64, b: i64) -> i64 { a + b }
fn main() {
let h = spawn(|| 40 |> add(2))
match h.join() {
Ok(v) => println!("answer: {}", v),
Err(e) => println!("worker failed: {}", e),
}
}
# Build the toolchain.
cargo build --workspace
# Create a new project.
./target/debug/gos new example.com/hello --path hello
cd hello
# Type-check, run, build.
gos check src/main.gos
gos run src/main.gos
gos build src/main.gos
# Lint, format, test.
gos lint .
gos fmt src/main.gos
gos test src/main.gos
# Drop into the REPL.
gosGossamer can call native (Rust) code through the [rust-bindings]
section of project.toml. A Rust crate that depends on
gossamer-binding registers its entry points with register_module!,
and the toolchain compiles and links it into the produced binary (or
the interpreter) - the bound functions are then use-able from .gos
source like any other module:
# project.toml
[rust-bindings]
echo-binding = { path = "echo-binding" }use echo::shout
fn main() { println!("{}", shout("hello")) }
The boundary uses the typed gossamer-binding ABI (integers, floats,
strings, tuples, vectors, Option / Result, opaque handles, byte
buffers, callbacks); a panic inside a binding is caught and surfaced as
a Result::Err. There is no source-level extern "C" item form - the
extern keyword is reserved (GP0016) and [rust-bindings] is the
single FFI surface. See SPEC.md section 12 and
example-external-libraries/ for two
end-to-end examples (a Gossamer-aware crate, and a plain published
crate wrapped thinly).
The runtime's stackful goroutines (corosensei) need a per-arch context-switch implementation. The current support matrix:
The supported target contract is the executable matrix in
conformance/target_matrix.tsv and the
matching supported-targets documentation.
Tier 1 executes the bytecode VM, JIT-enabled VM, and LLVM AOT binaries on
native CI for Linux x86_64/aarch64, Apple Silicon macOS, and Windows x86_64.
Linux x86_64/aarch64 musl AOT output is Tier 2: it is built from supported
hosts, executed natively or under QEMU, and compared with the pure bytecode
VM. Intel macOS is artifact-only pending execution evidence; armv7, riscv64,
and wasm are not supported execution targets.
Raspberry Pi OS 64-bit (and any aarch64 Linux) is first-class. Install
the linux-aarch64 release, then gos run works out of the box (the VM
and its in-process JIT are self-contained). To compile natively on the
Pi, also install system LLVM and a C compiler:
sudo apt-get install -y llvm clangBuild a Pi binary from a Linux, macOS, or Windows desktop. The musl-static target is the host-agnostic path (no target sysroot needed):
rustup target add aarch64-unknown-linux-musl
cargo build --release --target aarch64-unknown-linux-musl -p gossamer-runtime
gos build --release --target aarch64-unknown-linux-musl app.gos
# copy the static binary to the Pi and run it - no runtime depsFor a glibc (dynamic) Pi binary, target aarch64-unknown-linux-gnu; on a
Linux host install gcc-aarch64-linux-gnu, and on macOS/Windows supply an
aarch64 glibc sysroot via GOS_CROSS_SYSROOT. See SPEC §11.4 for the full
contract.
Support for various editors (VS Code, Neovim, etc) here - syntax and LSP support.
Lite Anvil supports Gossamer as a first class language (syntax & LSP).
Examples run through the bytecode VM by default (with optional deferred JIT tier-up) and compile in debug or release mode.
There are gaps to fill in the standard library, bugs and optimizations to find via real world usage.
This project is still early but starting to find its sea legs. Right now performance, resource usage, functionality, and productivity all feel very promising. But do not trust this yet.
My main goals are:
-
Making Gossamer reliable enough to run real production code, and trust.
-
Optimizing Gossamer to be Go-grade or better for performance and resource usage. This feels close: on several compute-heavy kernels the compiled tier already reaches or beats Go, and recent interpreter work has narrowed the scripting-mode gap substantially.
-
Building a reliable standard library to reduce the need to reach for third party libraries (using Golang as the gold standard, with small changes that feel right).
-
Writing some ecosystem libraries for key functionality (gRPC, Postgres, etc) that shouldn't be in the standard library, but are necessary for real work. (Very early).
-
Ensuring the developer experience fits the broad goals I have for a language that can replace or reduce my use of Go, Rust, Python, and F#.
cargo build --workspace
./target/debug/gos --versionLicensed under Apache-2.0. See LICENSE.