News

hsrs brings type-safe Haskell bindings to Rust APIs

hsrs tries to make Rust feel native on the Haskell side, replacing brittle FFI glue with generated bindings, newtypes, and safer resource handling.

Nina Kowalski··5 min read
Published
Listen to this article0:00 min
Share this article:
hsrs brings type-safe Haskell bindings to Rust APIs
Source: opengraph.githubassets.com

A narrower kind of interop

The sharp question behind hsrs is not whether Haskell and Rust can talk. It is whether they can talk without the boundary feeling like a border checkpoint. hsrs takes that problem head-on as a type-safe Haskell Rust bindings generator, built for developers who want Rust APIs exposed to Haskell without hand-writing brittle FFI glue.

AI-generated illustration
AI-generated illustration

That framing matters because the project is not trying to be a generic bridge for every language pairing. It is built around a specific, high-value use case: Rust as the safe implementation layer, Haskell as the caller, and the binding layer doing enough work that the two sides still feel idiomatic in their own worlds.

How hsrs works

The workflow is intentionally simple. You annotate Rust types and functions, run the generator, and get Haskell bindings back. Under the hood, hsrs uses proc macros to generate C FFI exports via safer-ffi, then hsrs-codegen reads the annotated Rust source and emits Haskell that looks much closer to handwritten code than to raw glue.

That generated Haskell is not just a pile of low-level wrappers. The repository says hsrs emits newtypes, ForeignPtr management, and pattern synonyms, which gives the Haskell side structure instead of a maze of pointers and manual cleanup. In practice, that means the binding layer is doing the boring, failure-prone work that usually makes mixed-language systems painful to maintain.

What “idiomatic” interop means here

In hsrs, idiomatic does not mean minimal. It means preserving the shape of the Rust API in a way Haskell can use naturally, while keeping type information intact as much as possible. The project supports common Rust types such as Result, Option, and collections, so the bridge can carry familiar Rust semantics across the boundary instead of collapsing everything into an opaque foreign blob.

That choice shows up in the way resources are handled too. On the Haskell side, hsrs leans on ForeignPtr-based memory management, which reduces the amount of manual resource handling you have to write and audit. For a cross-language layer, that is not a small convenience, it is one of the main defenses against leaks, double-frees, and the kind of lifecycle bugs that hide until production traffic finds them.

Why serialization is part of the story

The other half of the problem is data movement. For values that need to cross by value rather than by reference, hsrs uses Borsh serialization. That gives the project a structured way to move complex types across the language boundary without flattening everything into ad hoc byte handling.

Borsh stands for Binary Object Representation Serializer for Hashing, and the Rust implementation describes it as intended for security-critical projects, with consistency, safety, speed, and a strict specification at the center of the format. That is a useful fit for hsrs because the project is not merely passing pointers around. It is trying to preserve the Rust data model in a form Haskell can consume safely and predictably.

Why this is different from classic FFI glue

Classic FFI code tends to make the language boundary visible everywhere. You see it in manual marshalling, in explicit resource ownership code, and in all the little places where type information evaporates and gets replaced by defensive runtime checks. hsrs pushes in the opposite direction: the generator handles memory management, serialization, and type conversions, so the boundary recedes into generated code instead of dominating the API surface.

That is the real appeal for teams that care about maintenance cost. The project is built for strong typing rather than minimal wrappers, which means the output is meant to be usable, understandable, and safer to evolve. In mixed-language systems, that can matter as much as raw performance, because the cheapest binding layer is not the one with the fewest lines, it is the one that does not constantly need to be rewritten after the first API change.

The ecosystem context around hsrs

hsrs also sits inside a broader lineage of Haskell-Rust binding efforts. The older hs-bindgen project explicitly notes that earlier Rust-to-Haskell binding-generation work was no longer maintained, which makes hsrs part of a continuing attempt to replace hand-written FFI with something higher-level and more sustainable. That history is important because it shows this is a real pain point, not an abstract language-theory exercise.

The comparison point that most Rust developers will recognize is PyO3. GitHub describes PyO3 as Rust bindings for the Python interpreter, and it has become the best-known example of Rust acting as an interop hub for another ecosystem. hsrs is much smaller and far more niche, but it is aiming for a similar kind of ergonomic developer experience at the Haskell/Rust intersection.

Who actually benefits from adopting it

The clearest beneficiaries are Haskell teams that need to call into Rust and want the boundary to stay type-safe and maintainable. If the current alternative is handwritten FFI that leaks ownership rules into every call site, hsrs offers a much cleaner way to move data and expose APIs.

Rust teams can benefit too, especially when they are the ones publishing a library to another strongly typed language. In that case, hsrs turns Rust into a stable implementation layer that can be consumed from Haskell without making the Haskell side feel like it is living inside a foreign ABI experiment. The project is niche, but the niche is real: any team that wants stronger guarantees than classic glue code can usually provide is exactly the kind of audience that will feel the difference.

hsrs is not trying to make the language boundary disappear. It is trying to make it honest, typed, and boring in the best possible way. That is what makes the project interesting: not a grand story about replacing another language, but a very specific effort to keep Haskell and Rust talking without either side having to pretend the other one is foreign.

Know something we missed? Have a correction or additional information?

Submit a Tip

Never miss a story.

Get Rust Programming updates weekly. The top stories delivered to your inbox.

Free forever · Unsubscribe anytime

Discussion

More Rust Programming News