154 lines
5.1 KiB
Markdown
154 lines
5.1 KiB
Markdown
# 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.
|