integrations/haskell/notes/001-garnet.md
2026-03-24 14:26:09 +01:00

4.6 KiB

Garnet Findings

Date: 2026-03-24

Garnet repository: https://code.obsidian.systems/patrickaldis/garnet

Summary

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.

The repo demonstrates the pipeline. It is not 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 architecture is clear. The split between raw bindings and wrapper code is appropriate. The examples cover more than trivial FFI cases.

The project is still early. The build is custom, the dependencies are pinned to git revisions, and the Rust build script contains a workaround for upstream tooling issues. That is acceptable for an experiment, but it is a weak base for a maintained integration layer.

The main limitation is that code generation does not remove the manual work. The Haskell side still needs wrapper code to produce a usable API. The repo shows that the pipeline works. It also shows that the workflow still has overhead.

Pros

  • The architecture is easy to inspect.
  • The examples cover multiple FFI cases.
  • The split between generated and hand-written code is reasonable.
  • The repo is small enough to review quickly.

Cons

  • The build flow is manual.
  • The setup depends on alpha-stage or pinned upstream tooling.
  • The local header patch adds maintenance risk.
  • The wrapper layer still requires manual boilerplate.
  • There is no visible test, CI, or long-term maintenance plan.

Status

Current status: working prototype.

The repo is far enough along to prove the interop path and demonstrate the tooling choices. It is not far enough along to support repeated reuse without more work on build integration, test coverage, and dependency stability.

Reviewer verdict: useful experiment, not ready to treat as infrastructure.

Ecosystem Maturity

Rust/Haskell interop is uneven.

The low-level foundation is mature enough. Haskell has had an FFI story for years, and Rust can export or import 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 usable. 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: usable for a disciplined team, not 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. It is the better default.

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 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 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 worth watching but is still early-stage technology.

Practical Read On Garnet

Garnet is useful 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. It is a useful experiment and a warning about the current workflow cost.