5.1 KiB
Garnet findings
Date: 2026-03-24
Path inspected: ./tmp/garnet
Plain-English summary
./tmp/garnet is a small experiment for calling Rust from Haskell.
The flow is:
- Define Rust functions and data types.
- Export them as a C ABI.
- Generate a C header with
cbindgen. - Generate Haskell-side low-level bindings with
hs-bindgen. - Add hand-written Haskell wrappers on top.
- Run a demo executable to show the setup works.
This looks like a prototype or reference repo, not a finished product.
What it is
This is a mixed Haskell/Rust FFI demo.
It contains:
- a Cabal package named
garnet - a Rust static library in
./tmp/garnet/rust - Haskell modules for raw bindings and higher-level wrappers
- a small executable that exercises the API
- a shell build script
- a Nix flake for the development environment
What it is trying to prove
The repo appears to be testing whether this toolchain is practical:
Rust -> cbindgen -> C header -> hs-bindgen -> Haskell wrappers
It also tests whether the approach can handle more than trivial examples, including:
- C strings
- plain structs
- enums
- simple arithmetic
- recursive tree-shaped data
Key files
./tmp/garnet/rust/lib.rs- Rust definitions for exported FFI functions and example types
./tmp/garnet/rust/build.rs- generates
garnet_rs.hand patches it for compatibility withhs-bindgen
- generates
./tmp/garnet/lib/GarnetRs/Raw.hs- drives low-level binding generation from the generated header
./tmp/garnet/lib/GarnetRs/Wrapped.hs- adds Haskell-friendly wrapper types and conversion code
./tmp/garnet/exe/Main.hs- demo executable using the wrapper API
./tmp/garnet/build- custom build script for Rust + Cabal coordination
Easy reading of the design
Rust side
The Rust library exports a handful of example FFI functions:
hellohello_structhello_shapeaddsum_tree
These are not a product API. They are examples chosen to stress different interop cases.
Haskell side
The Haskell code is split into two layers:
GarnetRs.Raw- low-level generated bindings
GarnetRs.Wrapped- nicer Haskell-facing types and functions
This separation is a good design choice. It keeps generated code concerns away from the public API.
Critical assessment
This repo is technically promising, but clearly unfinished.
What looks good:
- It demonstrates the full end-to-end workflow.
- It goes beyond the easiest possible FFI examples.
- It keeps raw and wrapped APIs separate.
- It includes a dev shell, which improves reproducibility.
What looks weak:
- The build flow is manual and somewhat brittle.
- The project depends on pinned git sources and alpha-stage tooling.
- The Rust build script contains a workaround for upstream tooling issues.
- The Haskell wrapper layer still requires manual boilerplate.
- There is no obvious sign of tests, CI, or a stability story.
Pros of the approach
- Clear architecture: raw bindings and ergonomic wrappers are separated well.
- Good exploration value: useful for learning the actual friction points in Rust/Haskell interop.
- Covers realistic cases: includes enums and recursive data, not just simple integers.
- Small and understandable: the repo is compact enough to inspect quickly.
- Useful as a reference: someone exploring
hs-bindgencould learn from it.
Cons of the approach
- Custom build glue: the shell script and symlink step suggest the toolchain is not yet smooth.
- Dependency fragility: pinned git dependencies increase maintenance burden.
- Tooling immaturity: the repo already needs local workarounds for upstream issues.
- Manual wrapper overhead: generated bindings do not remove the need for hand-written API cleanup.
- Potential safety complexity: FFI, pointer conversions, and
unsafecode are easy to get wrong. - Weak production story: this setup does not yet look ready for long-term team use.
Status of the work
My assessment of current status:
- Stage: proof of concept / exploration
- Scope: enough code exists to show the full path works
- Maturity: decent as an experiment, weak as a reusable foundation
- Stability: uncertain because of upstream pins and workarounds
- Readiness: not production-ready
In simple terms: this looks like “we got it working” rather than “we finished the system.”
Likely next problems if this grows
If someone tried to turn this into a serious integration layer, the next hard problems would likely be:
- making the build process less custom
- reducing wrapper boilerplate
- keeping generated headers and bindings in sync
- handling more complex Rust types safely
- upgrading upstream tooling without breakage
- adding tests that catch FFI regressions early
Bottom line
./tmp/garnet is a useful and interesting interop prototype.
It succeeds as a demonstration that Rust -> C header -> Haskell bindings -> Haskell wrapper can work. But it also shows the current cost of that path: manual build glue, unstable dependencies, wrapper code, and reliance on tool-specific workarounds.
Overall: good experiment, useful reference, not mature infrastructure yet.