What Is hsrs?
hsrs is a Rust-to-Haskell FFI binding generator built by harmont-dev. hsrs is one of the best FFI Binding Generators tools for Rust and Haskell developers building type-safe native interop, and it requires Rust 1.85+ while generating idiomatic Haskell wrappers for annotated Rust types, methods, and enums. It handles memory ownership, serialization, and type conversion across the boundary with Borsh and ForeignPtr, which is the part most teams usually get wrong by hand.
Quick Overview
| Attribute | Details |
|---|---|
| Type | FFI Binding Generators |
| Best For | Rust and Haskell developers building type-safe native interop |
| Language/Stack | Rust 1.85+, Haskell, Borsh, C FFI |
| License | MIT OR Apache-2.0 |
| GitHub Stars | N/A as of Feb 2026 |
| Pricing | Open-Source |
| Last Release | N/A |
Who Should Use hsrs?
- Rust teams exposing core logic to Haskell that want generated wrappers instead of maintaining a parallel hand-written FFI layer.
- Haskell engineers integrating with existing Rust libraries who need
IO-safe wrappers, automatic cleanup, and predictable type conversions. - Library authors shipping a native SDK where
Result,Option,Vec, andStringshould map cleanly into Haskell without custom marshaling code. - Small platform teams that want fewer ABI bugs and less time spent debugging pointer ownership at the boundary.
Not ideal for:
- Projects that need broad multi-language binding generation, because hsrs is specifically optimized for Rust ↔ Haskell interop.
- Teams targeting 32-bit platforms, since
usizeandisizeare mapped toWord64andInt64and may truncate on 32-bit systems. - Codebases that cannot annotate Rust sources, because hsrs depends on explicit
#[hsrs::...]markers to drive generation.
Key Features of hsrs
- Annotation-driven code generation — hsrs uses Rust attributes like
#[hsrs::module],#[hsrs::function],#[hsrs::data_type], and#[hsrs::value_type]to discover what should cross the boundary. That keeps the FFI contract close to the Rust source instead of scattering glue code across a second repository. - Automatic ownership handling —
#[hsrs::data_type]becomes aForeignPtrnewtype in Haskell, with automatic cleanup on the Haskell side. That reduces the chance of leaks and double-frees when objects are created in Rust and consumed from Haskell. - Borsh-backed value transfer — complex values such as
String,Vec<T>,Option<T>, andResult<T, E>serialize through Borsh, which keeps the wire format deterministic and compact. ForPoint,VmError, and similar structs, hsrs derives the right Haskell instances so the data model stays aligned. - Direct C FFI for primitives — fixed-width integers and
boolare passed directly through the ABI, which avoids unnecessary serialization overhead for hot paths. That matters when you are moving small values in tight loops or calling into Rust on every request. - Enum pattern synonyms —
#[hsrs::enumeration]maps to aWord8newtype with Haskell pattern synonyms, so exhaustiveness stays readable without hand-writing constants. The generated API keeps enum usage close to idiomatic Haskell while still matching Rust representation constraints. - Type mapping for standard containers —
Result<T, E>becomesEither E T,Option<T>becomesMaybe T,Vec<T>becomes[T], andStringbecomesText. This is the difference between bindings that feel native and bindings that force every call site to unpack raw bytes. - Minimal runtime surface — the Haskell side only needs the
hsrsruntime package, and the Rust side depends on thehsrscrate. If you already ship Rust libraries, this keeps the dependency graph tight and easier to audit.
hsrs vs Alternatives
| Tool | Best For | Key Differentiator | Pricing |
|---|---|---|---|
| hsrs | Rust-to-Haskell FFI with generated safe wrappers | Annotation-based generation with Borsh, ForeignPtr, and Haskell-native type mapping | Open-Source |
| c2hs | Haskell bindings to C libraries | Mature C header interface for legacy APIs | Open-Source |
| bindgen | Generating Rust bindings from C/C++ headers | Parses headers into Rust types with broad C support | Open-Source |
| cbindgen | Exporting Rust APIs to C headers | Produces C headers from Rust crates for downstream consumers | Open-Source |
Pick c2hs if your source of truth is a C library and the Haskell side is the only consumer. Pick bindgen if your goal is Rust consumers of a C or C++ API, not Haskell wrappers.
Pick cbindgen if you want to publish a C-facing header from Rust and let other languages consume it later. If you are reviewing generated output or tracing runtime behavior across the ABI boundary, pair hsrs with Ghist for generated-diff review and OpenTrace for observing calls that cross the Rust/Haskell boundary.
How hsrs Works
hsrs works by turning annotated Rust modules into a typed interface specification, then emitting Haskell source that mirrors the Rust API shape. The central abstraction is a Rust module decorated with #[hsrs::module], which scopes the exported data types and methods so the generator can build a coherent set of wrappers instead of a pile of unrelated symbols.
The design is intentionally split between primitive ABI calls and serialized value transfer. Small scalars travel directly through the C ABI, while richer types move via Borsh, which lets hsrs preserve Rust semantics for Option, Result, Vec, and String without asking Haskell developers to manage raw pointers or manually decode bytes. That split is why the generated API can stay compact while still handling real application data.
cargo install hsrs-codegen
hsrs-codegen src/lib.rs -o Bindings.hs
The command above scans your Rust source, reads the hsrs annotations, and emits a Haskell module named Bindings.hs. After generation, your Haskell code imports the module and calls the exported functions like normal IO actions, while hsrs handles object lifetime through ForeignPtr and marshaling through Borsh where needed.
Pros and Cons of hsrs
Pros:
- Strong type mapping across the boundary — common Rust types land in Haskell as
Text,Maybe,Either, and standard numeric types, which reduces wrapper code. - Automatic memory cleanup — generated
ForeignPtrhandling removes a major source of leaks in manual FFI work. - Deterministic serialization — Borsh gives a stable binary format for complex values, which is easier to reason about than ad hoc encoding.
- Very small annotation surface — a few attributes are enough to describe modules, data types, value types, functions, and enums.
- Good fit for library authors — if your Rust crate already defines a clean API, hsrs turns it into a usable Haskell interface without a second implementation.
Cons:
- Rust 1.85+ requirement limits older toolchains and legacy CI images.
- 32-bit support is weak because
usizeandisizeare mapped to 64-bit Haskell types and may truncate on smaller targets. - Haskell-only output means it is not a general-purpose binding system for multiple target languages.
- Annotation dependence means unannotated Rust code will not be exported automatically.
- Serialization overhead exists for non-primitive types, so it is not ideal for ultra-low-latency micro-calls carrying large payloads.
Getting Started with hsrs
cargo install hsrs-codegen
# Add the runtime crate on the Rust side
# [dependencies]
# hsrs = "0.1"
# Generate Haskell bindings from annotated Rust source
hsrs-codegen src/lib.rs -o Bindings.hs
# Add the runtime package on the Haskell side
# build-depends: hsrs >= 0.1 && < 0.2
After generation, import Bindings from Haskell and call the exported functions directly. The first run is mostly about confirming that your Rust annotations compile cleanly and that the generated Haskell names match your module layout, especially if you use multiple exported structs or enums.
The main configuration work happens in your Rust source, not in a separate schema file. If your types use Result, Option, Vec, or custom structs, make sure those types derive or implement the traits required by hsrs so the generator can serialize them with Borsh and emit the matching Haskell instances.
Verdict
hsrs is the strongest option for Rust-to-Haskell native bindings when you want generated wrappers instead of hand-written FFI. Its biggest strength is automatic ownership and Borsh-based conversion for common types, and its main caveat is the 64-bit usize/isize mapping. Use hsrs if you control both sides of the ABI and want less FFI maintenance.



