Stream: show and tell

Topic: Roc interpreter using Rust


view this post on Zulip Brian Teague (Sep 17 2026 at 19:56):

So I've always wanted to code a hobby project of an interpreter of a strongly typed scripting language using type inference in rust because it has zero garbage collection and memory safety. That's how I first discovered Roc which is why I decided to put that hobby project on hold, since the design philosophy matched what I wanted to make.

With 0.1.0 coming up soon, I decided to try a one month test of a paid subscription to claude to see what slop I could build using vibe coding only, and it reminded me of that interpreter I always wanted to make. With Claude, I managed to successfully create a mostly working Roc interpreter called rocflight using Rust matching feature parity with the roc compiler. (Try out the snake example with it) https://github.com/B-Teague/rocflight. If you want me to take it down, let me know, because I don't want to take away from what you are accomplishing with Roc. Interestingly, the rocflight test command ran faster than roc test command on average, but that's probably because it's not including parse time vs roc including compile time.

I also got a working prototype of a lightdm login greeter that uses tauri (rust web backend) and leptos (rust web frontend). Think electron, but 100x smaller binary that uses webkit instead of the entire chrome browser engine as the backend, so you can create custom login screens using a custom CSS template.
https://github.com/B-Teague/tauri-greeter

These are prototypes.

PS: I went ahead and signed up as a sponsor for Roc.

view this post on Zulip Steve Howell (Sep 17 2026 at 20:05):

Did you look at the original Rust compiler for Roc for inspiration?

(As an aside, I have also written a Rust interpreter for a language that shares some stuff in common with Roc. My Rust compiler also transpiles to Roc. I intend to fully announce it after it gets a little more polish, but here's the link: https://github.com/showell/rust-codex-compiler.)

view this post on Zulip Brian Teague (Sep 17 2026 at 20:11):

I've reviewed the Rust compiler before they switched to zig, but this was solely an experiment of what can I build with AI without touching a single line of code. I went through multiple optimization passes and told it to use a register VM module (not sure if it's actually doing real bytecode or not, but it produced a mostly working binary) And it produced some fast results.

I'm curious what Anton or Luke think of it to see if it is something I should get fully familiar with and maintain long term or just delete cause there's not real practical use for it.

view this post on Zulip Steve Howell (Sep 17 2026 at 20:24):

There is some chance that I would borrow your code to transpile Roc to other targets.

When I used Claude to write the similar program I mentioned above, Claude chose to have the interpreter directly walk the AST. We briefly discussed a VM approach but instead did some pre-processing of the AST to fix the most egregious performance issues from re-computing the same things over and over again in the AST.

view this post on Zulip Brian Teague (Sep 17 2026 at 20:27):

Yeah I definitely put a lot of focus on correct types and building the same AST with sugared and desugared roc code, so a roc file with no types and the same roc file with explicitly defined types generated the same AST along with comparing it against roc experimental-lsp.

view this post on Zulip Luke Boswell (Sep 17 2026 at 21:53):

I had a lot of fun building the CIR interpreter in Zig, and for a while that was all we had in the new compiler... and then it became clear that handling polymorphism correctly was a whole beast of its own and we were effectively rebuilding or covering a lot of the same ground as the compiler pipeline.

So we had this interpreter which was running from the syntax tree and checked types and reinventing things (and only mostly correctly), and then concurrently we were building the backends that were doing the whole "polymorphic defunctionalization... thing" based on Ayaz's COR work. The bugs in the interpreter were getting really hairy to unwind, it was becoming a bit of a mess.

So we refactored the Roc interpreter to target LIR instead and it was given nice monomorphic types like all the other backends. This eliminated a hug amount of complexity in the interpreter. So much so that I'm not sure anyone has even thought about the interpreter for such a long time... it just sits there doing its thing as the oracle supporting all of the tests like a trooper. I'm sure there are enormous performance improvements we could make on that, it has the foundations to be a very fast interpreter.

There is some value in having alternative implementations or compiler diversity. We theorised that an interpreter could be very fast for development, and that was the whole CIR experiment ... but it was getting difficult to make it correct and was sucking effort away from the compiler at the time.

I think building an interpreter in Rust is a lot of fun. I think exploring different approaches to develop quality software and build guardrails for Agentic work is fun. I think the approach of using roc as an oracle is a great idea. If you wanted a ready made set of tests to stress your implementation, src/eval/test has a lot you can use.

I'm not sure what your goals are. Personally I'd love to see the roc interpreter get some performance love, maybe you could work on both at the same time?

view this post on Zulip Steve Howell (Sep 17 2026 at 23:32):

Oracles are super important. The beauty of agents is that you can literally create a Rust-interprets-Roc solution in a month of "vibe coding". I know that Brian Teague wasn't doing "vibe coding" in the usual sense. Lots of engineering to understand the compiler pipeline and generate the same AST, etc.

view this post on Zulip Brian Teague (Sep 18 2026 at 00:14):

I told claude to run a benchmark comparing rocflight to roc/src/eval/tests which compares against roc --opt=interpreter and published to readme. Results look very promising with the register VM implementation, assuming feature parity is correct.

benchmark        rocflight   roc-interp   roc-dev   interp/rocflight
calls                  7ms         14ms      29ms          2.0x
closure_in_loop       10ms        748ms      69ms         74.8x
iter_range            86ms      35661ms     479ms        414.7x
list_ops               4ms        148ms      72ms         37.0x
loop                  12ms       3185ms      88ms        265.4x
matching              24ms       1252ms      68ms         52.2x
records               10ms        787ms      67ms         78.7x
strings                7ms        192ms     127ms         27.4x

view this post on Zulip Luke Boswell (Sep 18 2026 at 00:21):

Super impressive... does it pass all the eval tests already?

view this post on Zulip Brian Teague (Sep 18 2026 at 00:21):

My design goals for rocflight are:
Leverage Rust for memory safety, speed, and zero garbage collection
Make a Roc interpreter that has full feature parity with the Roc compiler and correctly hooks into platforms
Make rocflight run as close to bare metal as possible using a register VM

view this post on Zulip Brian Teague (Sep 18 2026 at 00:24):

Oof, yeah only 8 of the 12 tests were run and it didn't build the zig eval tests, just ran 8 of the 12 roc source tests using roc --opt=interpreter, so I need to figure out where it's breaking, fix that, have it build the zig eval tests, and compare that way.

Be right back after these messages.

view this post on Zulip Luke Boswell (Sep 18 2026 at 00:39):

I think there are about 2,500 eval tests :grinning_face_with_smiling_eyes:

view this post on Zulip Luke Boswell (Sep 18 2026 at 00:44):

The easiest thing to hook it up would be to have claude add rocflight as another backend in the eval test harness driver, it can run that alongside the other backends and compare the results -- it runs each test in a separate process using all the system threads, compares the results using Str.inspect, and has a lot of instrumentation to help narrow in on issues... things like timings are included too. So you wouldn't need to build a new harness, I'd just use that.

view this post on Zulip Luke Boswell (Sep 18 2026 at 00:45):

I iterated on that many times over with Claude -- there was a lot of engineering in the harness itself so it would be really Agentic friendly.

view this post on Zulip Luke Boswell (Sep 18 2026 at 05:09):

After this discussion ... I just had to throw Fable at this and see what shakes out. Interesting read.

https://gist.github.com/lukewilliamboswell/838a1f5ef7d3f21b58737a3fde458010

I'm probably not in a position to try this until next week. If anyone wants to work on making Roc's interpreter fast, this could be a fun project. :rock_on:

view this post on Zulip Steve Howell (Sep 18 2026 at 12:06):

Luke's article from Fable has a great phrase in item 6: "stop re-deriving per execution what was knowable earlier".

My mantra to Claude is: Carry earned knowledge forward. It was amazing how many times you have to remind even Fable of this if you're doing compiler/interpreter work. Claude will paper over stuff that's missing in the upper layers of the compiler instead of just changing the upstream data structures not to be lossy.

view this post on Zulip Anton (Sep 18 2026 at 14:24):

PS: I went ahead and signed up as a sponsor for Roc.

Thanks @Brian Teague :heart:

view this post on Zulip Brian Teague (Sep 21 2026 at 17:11):

Finally got rocflight passing all eval tests and did several optimization passes.

=== Performance Summary (ms) ===
  Phase         Min      Max     Mean   Median   StdDev      P95    Total     N
  -------- -------- -------- -------- -------- -------- -------- --------   ---
  parse         0.0      2.0      0.1      0.1      0.1      0.2    276.7   2087
  can           0.0      2.6      0.2      0.2      0.2      0.4    500.5   2086
  check         0.2    166.4      1.1      0.9      3.7      2.1   2278.5   2086
  interp        1.4     21.8      3.7      3.4      1.5      6.4   7183.6   1953
  dev           1.1     79.6      3.5      3.0      3.2      6.7   6758.0   1953
  wasm         11.1    120.5     23.2     21.5      7.3     35.6  45270.1   1953
  rocflight      1.1     83.3      4.5      4.0      3.6      8.0   9132.6   2024

view this post on Zulip Anton (Sep 21 2026 at 17:20):

I'm not sure how the rows in the table should be compared, does rocflight do it's own parsing and is the parse row from the current compiler?

view this post on Zulip Brian Teague (Sep 21 2026 at 17:26):

Yes, rocflight does its own parsing and the parse row is from roc's compiler.

The table is printed by roc's own harness (roc-compiler/src/eval/test/parallel_runner.zig:2711), which runs each eval test through its frontend and then through each backend in a forked child. Each row is one phase's per-test durations, aggregated over N tests.

┌───────────┬────────────┬─────────────────────────────────────────────────────────────────┐
│    Row    │ Whose code │                       What's being timed                        │
├───────────┼────────────┼─────────────────────────────────────────────────────────────────┤
│ parse     │ roc        │ roc's parser on the test source                                 │
├───────────┼────────────┼─────────────────────────────────────────────────────────────────┤
│ can       │ roc        │ roc's canonicalization                                          │
├───────────┼────────────┼─────────────────────────────────────────────────────────────────┤
│ check     │ roc        │ roc's type checker                                              │
├───────────┼────────────┼─────────────────────────────────────────────────────────────────┤
│ interp    │ roc        │ roc's tree-walking interpreter evaluating the program           │
├───────────┼────────────┼─────────────────────────────────────────────────────────────────┤
│ dev       │ roc        │ roc's dev (self-hosted native) backend: codegen + run           │
├───────────┼────────────┼─────────────────────────────────────────────────────────────────┤
│ wasm      │ roc        │ roc's wasm backend: codegen + run in the wasm runtime           │
├───────────┼────────────┼─────────────────────────────────────────────────────────────────┤
│ rocflight │ rocflight  │ wall time of spawning your target/release/rocflight on the test │
└───────────┴────────────┴─────────────────────────────────────────────────────────────────┘

llvm is in the code too (BACKEND_NAMES at line 308) but doesn't print here because no test recorded a non-zero llvm time — rows are only collected if (t.<phase>_ns > 0) (line 2701).

Two things about the last row specifically:

- It's not comparable to interp. rocflight's number is measured around runRocflight (line 750), which mkdir's a scratch dir under /tmp, writes main.roc plus any import modules, forks/execs the binary, and waits. So it includes process startup and rocflight's own parse + canonicalize + check + run — i.e. everything rows 1–4 measure separately for roc, plus I/O. roc's backends are forked too, but from an already-compiled in-process module, so their rows exclude the frontend.
- N = 2024 vs 1953. The extra ~71 are the "problem" tests (runTestProblem, line 1608): programs roc is expected to reject. Those never reach roc's backends, so interp/dev/wasm don't record them, but rocflight is still timed there because the harness checks it rejects them too. Similarly parse N=2087 vs can 2086 — one test dies at parse.

view this post on Zulip Brian Teague (Sep 21 2026 at 17:32):

The builtin.roc is now precompiled into bytecode for the VM, so rocflight only has to parse starting at main!

view this post on Zulip Brian Teague (Sep 21 2026 at 17:54):

I added a learning.md file that gives an overview of the interpreter and how it works.

view this post on Zulip Brian Teague (Sep 21 2026 at 17:54):

https://github.com/B-Teague/rocflight

view this post on Zulip Steve Howell (Sep 24 2026 at 13:46):

Hi @Brian Teague. Just a heads up that you may see some PRs coming for rocflight. I have an interesting corpus of Roc programs that are already exposing some gaps with rocflight. As I mentioned in #show and tell > Roc interpreter using Rust @ 💬, I have a similar project that I'm working on. If you don't want the contributions, that's fine too, but I am instructing Claude to try to produce PRs with good test cases for your Claude to consume.

view this post on Zulip Steve Howell (Sep 24 2026 at 15:45):

There are now 5 PRs and 3 issues on https://github.com/B-Teague/rocflight. This may grow. :slight_smile:


Last updated: Sep 24 2026 at 15:59 UTC