Map

Rye

Wiki entitylanguagegoembedded-scriptinghomoiconicGo โ†ณ show in map Markdown
title
Rye
type
entity
summary
Homoiconic general-purpose language in pure Go, designed to be embedded as a config or scripting layer with per-word capability registration
tags
language, go, embedded-scripting, homoiconic
language
Go
created
2026-05-11
updated
2026-05-11

Rye is a general-purpose language written in pure Go (no CGO), maintained by the refaktor org. It's distributed both as a standalone interpreter and as a Go library you can import to embed in your own application. The website is ryelang.org and the canonical blog is ryelang-blog.

The unusual design choice: Rye has no reserved forms. There is no hardcoded if, fn, loop, or arithmetic in the evaluator. Every active word is a library-level function. The base evaluator can only load source text into blocks of Rye values and bind values to words (different word types โ€” set-words, mod-words โ€” are part of the syntax). Everything else is added by registering builtins.

This is what makes the whitelist-capability-config story work. When you embed Rye in a Go app, the config author starts with nothing and you decide one RegisterBuiltinsFilter at a time what they can do.

Word types and syntax notes

Rye has multiple word kinds carrying meaning in the syntax itself:

  • name: โ€” set-word, binds a value to a name (const by default)
  • name:: โ€” mod-word, rebinds an existing name
  • Name โ€” capitalized, generic method dispatch
  • _+, _* โ€” underscore prefix marks an "op-word", an operator-style call
  • Block delimiters { ... } create a block of values, ( ... ) groups expressions

The language is not whitespace-sensitive but does require spaces between every token, including around parens. The article walkthrough config looks visually like YAML but evaluates as code.

Embedding API

The Go side uses three packages:

import (
    "github.com/refaktor/rye/env"
    "github.com/refaktor/rye/evaldo"
    "github.com/refaktor/rye/loader"
)

ps := env.NewProgramState()
blk := loader.LoadString(raw, false, ps)
// type-check blk for env.Error, then:
evaldo.EvalBlock(ps, blk.(env.Block))
// inspect ps.Ctx for bound names, ps.Res for the last result

Capability registration is two functions:

  • evaldo.RegisterBuiltinsFilter(ps, []string{"if", "_+", "any"}) โ€” whitelist from the built-in pool
  • ps.RegisterBuiltin(name, arity, doc, fn) โ€” add a custom Go function as a Rye word

Execution caps live on the program state: ps.MaxCallDepth, ps.MaxOps. Default zero = unlimited.

Live REPL

enter-console is a registerable builtin that drops the running Go process into a Rye REPL with the current evaluation context populated. From there you can list bindings (lc), inspect values (probe), or rebind via mod-words. On Ctrl-c, the host process continues evaluating with the modified context. Useful for config debugging in development; you'd remove the registration in production.

Comparison with Starlark

Both are languages designed for embedding. Starlark is more mature, Python-shaped, and used at scale by Bazel, Buck, and friends. Differences the Rye author calls out:

  • Starlark gives you if, for, def unconditionally โ€” you can't take them away
  • Starlark's modules are mostly all-or-nothing
  • Rye lets you grant _+ without _*, if without loop
  • Rye aims to be a general-purpose language; Starlark is intentionally a config DSL

Status and caveats

The author calls Rye "a language in development" and the closing of ryelang-whitelist-config explicitly says "this is capability control, an exploration, not a security sandbox or something you should go and use today." Rye code runs in your process; unsafe builtins escalate immediately. The interesting bit is the design pattern, not the production-readiness pitch.

Repo: github.com/refaktor/rye. Pure Go, no CGO dependencies. License on the repo.

Sub-pages