# 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: 1. Define Rust functions and data types. 2. Export them as a C ABI. 3. Generate a C header with `cbindgen`. 4. Generate Haskell-side low-level bindings with `hs-bindgen`. 5. Add hand-written Haskell wrappers on top. 6. 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.h` and patches it for compatibility with `hs-bindgen` - `./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: - `hello` - `hello_struct` - `hello_shape` - `add` - `sum_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-bindgen` could 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 `unsafe` code 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.