# Garnet Findings Date: 2026-03-24 Path inspected: `./tmp/garnet` ## Summary `./tmp/garnet` is a proof-of-concept for Rust/Haskell interop. The repo is testing a specific pipeline: Rust exports a C ABI -> `cbindgen` emits a header -> `hs-bindgen` produces Haskell bindings -> hand-written Haskell wrappers clean up the API. It succeeds as a demo. It does not read as production-ready infrastructure. ## What It Contains - `./tmp/garnet/rust/lib.rs` - Rust FFI examples: strings, structs, enums, arithmetic, recursive trees. - `./tmp/garnet/rust/build.rs` - header generation plus a local patch for `hs-bindgen` compatibility. - `./tmp/garnet/lib/GarnetRs/Raw.hs` - low-level binding generation from the generated header. - `./tmp/garnet/lib/GarnetRs/Wrapped.hs` - Haskell-facing wrapper types and conversions. - `./tmp/garnet/exe/Main.hs` - demo executable that exercises the bridge. - `./tmp/garnet/build` - custom build glue for Cargo and Cabal. ## Review The project makes the intended architecture clear. Separating raw bindings from the wrapper layer is the right call, and the examples are broad enough to be useful. This is more than a toy "add two numbers" demo. That said, the repo still looks early. The build is custom, the dependency stack is pinned to git revisions, and the Rust build script contains a workaround for upstream tooling issues. That combination is acceptable in an experiment, but it is a weak base for a maintained integration layer. The biggest limitation is that code generation does not remove the hard part. The Haskell side still needs manual wrapper code to turn raw bindings into something ergonomic. The repo proves that the pipeline works, but it also shows that the pipeline is not yet cheap. ## Pros - The architecture is clean enough to follow. - The examples cover realistic FFI shapes, not just trivial functions. - The split between generated and hand-written code is sensible. - The repo is small enough to serve as a reference implementation. ## Cons - The build flow is manual and brittle. - The setup depends on alpha-stage or pinned upstream tooling. - The local header patch is a red flag for maintainability. - The wrapper layer still carries noticeable manual boilerplate. - There is no obvious sign of tests, CI, or a long-term stability plan. ## Status Current status: working prototype. It appears far enough along to prove the interop path and demonstrate the tooling choices. It does not appear far enough along to support repeated reuse without more work on build integration, test coverage, and dependency stability. In reviewer terms: promising experiment, useful reference, not ready to trust as infrastructure. ## Ecosystem Maturity Rust/Haskell interop is uneven. The low-level foundation is mature enough. Haskell has had a solid FFI story for years, and Rust is comfortable exporting or importing a C ABI. If the boundary is narrow and C-like, the approach is viable. The weak part is the tooling layer. Header generation on the Rust side is serviceable. Binding generation on the Haskell side is improving, but it still looks early. The Garnet repo reflects that gap exactly: the ABI path works, but the workflow still needs custom glue and hand-written cleanup. Reviewer verdict: mature enough for disciplined teams, not mature enough to feel easy. ## Calling Direction ### Calling Rust From Haskell This is the easier direction. Rust can expose a conventional C ABI, and Haskell can consume it through its normal FFI mechanisms. That keeps the ownership model and runtime story relatively simple, at least compared with the reverse direction. This is the path Garnet takes, and it is the right first choice. ### Calling Haskell From Rust This is the harder direction. Once Rust starts calling into Haskell, the GHC runtime becomes part of the design. That raises the complexity around runtime initialization, threading, callbacks, shutdown, and operational correctness. It is possible, but it is a sharper tool and a worse default. Reviewer verdict: call Rust from Haskell when possible; only call Haskell from Rust when there is a strong reason. ## Toolchain Recommendation Current best default: - keep the ABI small and C-shaped - use Rust `extern "C"` plus `repr(C)` at the boundary - use `cbindgen` to emit headers - keep Haskell bindings thin - write the final Haskell API by hand That is not glamorous, but it is the most credible path today. For teams that want the lowest risk, hand-written Haskell FFI bindings are still easier to trust than a large generated surface. For teams exploring automation, `hs-bindgen` is interesting but still reads as early-stage technology. ## Practical Read On Garnet Garnet is valuable because it shows both sides of the story. It shows that the basic interop path works. It also shows that the surrounding tooling is not yet smooth enough to disappear into the background. That makes it a good experiment and a fair warning.