First, a disclaimer. I have been the manager in this project, I don’t know how it works. Several LLMs have spent a few hundred million tokens writing it :-)
So, I leave you to their announcement
py-crystal is a framework for writing Python extension modules in Crystal: statically compiled, GC-managed, and expressive — with the ergonomics of PyO3 for Rust and nimpy for Nim. Crystal exceptions become typed Python exceptions, ownership across the two heaps is handled automatically, and the whole C-API plumbing layer is generated at compile time from a small DSL, in either an annotation style or a block style.
A taste — a word counter that Python can iterate, size, compare, and add:
require "pycr"
Pycr.pyclass WordCounter, "demo.WordCounter" do
pynew def initialize
@words = [] of String
end
pymethod def add(word : String) : Int32
@words << word
@words.size
end
pyiter def each : Iterator(String) # for w in wc
@words.each
end
pylen def size : Int32 # len(wc)
@words.size
end
pyadd def plus(other : WordCounter) : WordCounter
merged = WordCounter.new
(@words + other.@words).each { |w| merged.add(w) }
merged
end
end
from demo import WordCounter
a, b = WordCounter(), WordCounter()
a.add("crystal"); a.add("python")
b.add("static")
assert len(a) == 2
assert [w for w in a] == ["crystal", "python"]
assert (a + b).size == 3
assert a > b # rich comparison on word count
class Loud(WordCounter): # Python subclassing, super() included
def add(self, word):
return super().add(word.upper())
Everything is generated at compile time from those signatures: argument conversion with TypeError on mismatch, IndexError mapped to Python’s IndexError, iterators that survive GC cycles mid-stream. And the hard problems of hosting a second runtime inside CPython are solved and tested — foreign Python threads get full access to Crystal’s fiber scheduler through a custom execution context with the GIL released, Boehm’s collector coexists with CPython’s through a serialized collection policy, and ownership across the two heaps is handled by a pin registry plus owned references. The whole thing passes a stress gauntlet and ships as one binary validated against CPython 3.11 through 3.14.
It’s fast, with a real consumer to prove it: examples/tartrazine wraps the tartrazine highlighter in ~150 lines of DSL and benchmarks 11–12× faster than Pygments on real files (0.8 ms vs 9.1 ms at 300 lines) with --release, and ~0.1 ms of per-call overhead. The project is MIT-licensed at github.com/ralsina/py-cr — a self-contained wheel (Linux x86_64, CPython 3.11–3.14) is on the v0.1.0 release, installable with pip install <that wheel URL>, with CI building and smoke-testing a fresh wheel on every push.