asopi techOSS Developer
asopi tech
asopi techOSS Developer
Getting Started with Roc: A Pure Functional Language Meant to Ride Alongside Rust

[July 2026 edition]

Getting Started with Roc: A Pure Functional Language Meant to Ride Alongside Rust

Published: Jul 3, 2026
Reading time: ~6 min

Introduction

Hello, Asopi Tech here.

In the previous article — “Getting Started with Nim: A Multi-Paradigm Language That Transpiles to C” — I introduced Nim, one candidate AlopexDB is evaluating for its SQL parser rewrite. This time we look at the other star of the same investigation: Roc.

Roc is a relatively new pure functional language spearheaded by Richard Feldman, creator of Elm. Its distinctive design — “Rust host + Roc application” — is an attempt to bring functional programming into the systems world.

What kind of language is Roc?

Roc features:

  • Pure functional — side effects are explicit; referential transparency holds
  • Hindley-Milner type inference — strong static typing with virtually no annotations
  • First-class ADTs + pattern matching — Tag Unions ([Ok val, Err e]) sit at the center of the language
  • Perceus reference counting — a GC that aims for zero pause time and no scheduler tuning
  • Compiles to native via LLVM — produces native binaries
  • Platform / Host model — syscalls and side effects live in the “Platform”; the app is written as pure functions

The syntax feels very close to Elm or Haskell, and is unusually approachable at first read. Here’s a snippet from AlopexDB’s Roc SQL lexer trial (alopex-db/alopex experiment/sql-parser-trial):

Token : [Select, From, Where, Ident Str, IntLit I64, Star, Comma, Eq]

keyword : Str, Token -> Parser Utf8 Token
keyword = |text, tag|
    const(tag) |> skip(string(text))

token : Parser Utf8 Token
token = one_of([
    keyword("SELECT", Select),
    keyword("FROM", From),
    string("*") |> map(|_| Star),
    string(",") |> map(|_| Comma),
    many1(alpha) |> map(|chars| Ident(Str.from_utf8(chars))),
])

If you’ve touched Elm or F#, the pipeline operator |> and the way tokens are pattern-matched into shape should feel natural.

Where is Roc as of mid-2026 — “not even 0.1 yet”

Roc is genuinely young. Its status in June 2026:

Version history

ReleaseDateNotes
alpha12025-01-29First alpha release
alpha2-rolling2025-01-29Massive syntactic overhaul
alpha3-rolling2025-02-26
alpha4-rolling2025-08-26Latest. Build speed improvements, new builtins

The official position is explicit: “Roc is not ready for a 0.1 release yet.” No 1.0 timeline is published.

Breaking changes in alpha2 (January 2025)

alpha2 shipped sweeping breaking changes:

ChangeBeforeAfter
Function callsfunc argfunc(arg)
Naming conventioncamelCasesnake_case
Error handlingTask type! suffix + ? operator
String interpolation$(expr)${expr}
Logical operators&& / ||and / or
Tag constructionOk valOk(val)

A roc format --migrate migrator ships with the language, but the Task! rewrite is manual. The Alopex team reports 24 manual fixes were required for their SQL parser trial.

Expect more of the same as long as the language is in alpha.

The Zig compiler rewrite

The big infrastructure news is that the Roc compiler’s LLVM codegen has been rewritten from Rust (~18,000 lines) to Zig (~1,700 lines).

ItemDetail
Size18,000 lines Rust → 1,700 lines Zig (10x shrink)
ArchitectureMono IR → Canonical IR (pre-monomorphization)
Not yet implementedmatch expressions, lambdas, complex unions, refcounting, debug info
Top priorityFinish builtins (Issue #9596)

Cutting an overweight Rust compiler and rebuilding it around Zig’s comptime is a bold call. Note that Roc hosts are still written in Rust — basic-webserver runs on Rust + hyper + tokio. Splitting “the language you write your compiler in” from “the language you write your app hosts in” is very much on-brand for Roc.

Community

  • GitHub 5,700 stars / 387 forks / 43,653 commits
  • Active Zulip chat
  • Codebase is 92.9% Zig

What makes Roc interesting

Pure functional + ADT-first

AST definitions fall out naturally with Tag Unions, just like Elm:

Ast.roc
SqlNode : [
    Ident Str,
    StrLit Str,
    IntLit I64,
    BoolLit Bool,
    NullLit,
    StarLit,
    BinOp { op : BinaryOp, left : SqlNode, right : SqlNode },
    UnOp { op : UnaryOp, operand : SqlNode },
    FnCall { name : Str, args : List SqlNode },
    ColRef { table : Str, column : Str },
    SelectStmt {
        columns : List SqlNode,
        from : List SqlNode,
        where : [Some SqlNode, None],
        # ...
    },
    InsertStmt { table : Str, columns : List Str, values : List SqlNode },
    # ...
]

In Rust you’d reach for Box<SqlNode> and want to write #[allow(clippy::large_enum_variant)]. In Roc, Box never enters the user code — the runtime places things on the heap and manages them with Perceus refcounting.

Perceus reference counting

Roc’s GC strategy is called Perceus, borrowed from research on the Koka language. In short:

  • Static analysis places refcount ops → minimizes atomic ops at runtime
  • No stop-the-world
  • No manual tuning
  • Aims to unlock linear-type-based “in-place update” optimizations down the road

Platform / Host model

The most distinctive part of Roc is its Platform / Host model. All side effects — file IO, HTTP, TCP — live in the Platform. The app is a collection of pure functions.

Two major Platforms:

PlatformVersionOverview
basic-cliv0.20.0 (2025-08)Files, HTTP, TCP, CLI args
basic-webserverv0.13.1 (2026-01)Web server backed by Rust (hyper + tokio)

A Rust-written host loads the Roc app via C ABI:

Roc App (pure functions)
    ↓ exported as C ABI (roc_app_main, etc.)
Rust Host (side effects)

OS / Network / DB

This aligns very neatly with AlopexDB’s “Rust storage engine + Roc parser” blueprint — the language boundary maps cleanly to a responsibility boundary.

Container development environment

Roc doesn’t ship an official Docker image yet, but curl + tar on a binary release gets you all the way there.

Dockerfile
FROM debian:bookworm-slim AS roc-env

RUN apt-get update && apt-get install -y \
    curl ca-certificates gcc libc6-dev \
  && rm -rf /var/lib/apt/lists/*

ARG ROC_VERSION=alpha4-rolling
RUN curl -OL https://github.com/roc-lang/roc/releases/download/${ROC_VERSION}/roc-linux_x86_64-${ROC_VERSION}.tar.gz \
  && tar xzf roc-linux_x86_64-${ROC_VERSION}.tar.gz \
  && mv roc-linux_x86_64-${ROC_VERSION} /opt/roc \
  && rm roc-linux_x86_64-${ROC_VERSION}.tar.gz

ENV PATH="/opt/roc:${PATH}"
WORKDIR /app

For reproducibility, Nix Flake works too:

flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    roc.url = "github:roc-lang/roc";
  };

  outputs = { nixpkgs, roc, ... }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
      rocPkgs = roc.packages.${system};
    in {
      devShells.${system}.default = pkgs.mkShell {
        buildInputs = [ rocPkgs.cli ];
      };
    };
}

Once you have the binary, container setup cost matches Nim. The only differentiator is whether an official image exists; inside a container everything reduces to “curl + tar.”

Runtime performance — data isn’t out yet

Honestly, Roc-vs-Rust benchmarks aren’t yet public. The official goal is “faster than mainstream GC languages (Go, C#, Java, JS),” but there’s no quantitative comparison with Rust or Nim at this time.

That said, LoC and build times are already very promising (Alopex trial, current implementation):

MetricRocRust
Implementation LoC1,0492,964
release build4.7 s165 s
dev build0.8 s
Test runs (excl. build)0.3 s0.01 s
Projected SQLite-scale (dev)~5 s
Projected SQLite-scale (tests)~2 s

release builds are slightly slower than Nim, but the dev-build feedback loop (0.8 s now → about 5 s at SQLite scale) is Roc’s real weapon.

Watch-outs when operating Roc

Real pain points hit during the AlopexDB trial:

  • --linker=legacy required on Linux — surgical linker issue #3609 is still open
  • basic-cli URL hash changes across versions — Dockerfile maintenance overhead
  • Unused imports are compile errors (exit code 2) — you must fix them
  • .roc file naming rules are strict — CamelCase module names must match
  • API changes are frequent — see the 24 fixes above

The language design is refined, but production adoption is premature. The pragmatic stance is: run a PoC / experimental branch and evaluate ahead of a proper 1.0.

Who is Roc for, right now?

Roc is worth trying if you fit any of these:

  • Elm or Haskell veteran who wants to bring functional programming closer to systems work
  • You want to write AST definitions, parsers, or pure computation cores with GC-backed safety
  • You’re curious about the “Rust host + functional app” architecture
  • You want to follow language-implementation research (Perceus, Platform/Host, Canonical IR)

If you need to ship production services today or want a well-populated ecosystem, stay with Nim / Rust / Go for now.

Still — once the Zig compiler rewrite lands and the road from 0.1 to 1.0 is visible, Roc will offer a rare combination of “pure functional × native performance × Rust interop.” It’s very much worth watching now, before it goes mainstream.

Next up: an implementation-level comparison of both Nim and Roc SQL parsers side-by-side. See the next post — “Rust Is Not a Silver Bullet — Language Parsers Belong in Nim or Roc” — for the deep dive.

See you next time!

References