2026-03-24 14:34:09 +01:00
|
|
|
# Haskell Interop Demo
|
|
|
|
|
|
|
|
|
|
This directory contains a small Haskell project for the repository's Rust/Haskell integration work.
|
|
|
|
|
|
|
|
|
|
The project demonstrates both directions:
|
|
|
|
|
|
|
|
|
|
- Haskell calling into Rust through a C ABI exposed by the Rust crate.
|
|
|
|
|
- Rust calling back into Haskell through a Cabal `foreign-library`, with explicit GHC RTS initialization.
|
|
|
|
|
|
|
|
|
|
## Layout
|
|
|
|
|
|
|
|
|
|
- `src/Interop/Shared.hs` - pure shared logic plus the C-compatible struct layout used at the boundary.
|
|
|
|
|
- `src/Interop/Exports.hs` - Haskell functions exported to C for Rust to call.
|
|
|
|
|
- `app/RustClient.hs` - Haskell imports for the Rust C ABI.
|
|
|
|
|
- `app/Main.hs` - Haskell executable that demonstrates the Haskell -> Rust path.
|
|
|
|
|
- `test/Spec.hs` - small pure tests that avoid crossing the FFI boundary.
|
|
|
|
|
|
|
|
|
|
The code keeps the FFI surface small on purpose. The boundary uses:
|
|
|
|
|
|
|
|
|
|
- integers
|
|
|
|
|
- a fixed C-shaped struct
|
2026-03-27 09:20:31 +01:00
|
|
|
- borrowed slices as `ptr + len`
|
|
|
|
|
- owned returned buffers as `ptr + len + cap`
|
2026-03-24 14:34:09 +01:00
|
|
|
- owned C strings with an explicit free function on each side
|
|
|
|
|
|
|
|
|
|
That is enough to demonstrate the main challenges without pulling in code generation or large bindings.
|
|
|
|
|
|
2026-03-27 09:20:31 +01:00
|
|
|
The current examples include:
|
|
|
|
|
|
|
|
|
|
- scalar stats and message passing
|
|
|
|
|
- borrowed `i32` slices sent across the boundary
|
|
|
|
|
- owned `i32` buffers returned across the boundary
|
|
|
|
|
- borrowed raw byte buffers with embedded zero bytes
|
|
|
|
|
- owned raw byte buffers returned across the boundary
|
|
|
|
|
|
2026-03-24 14:34:09 +01:00
|
|
|
## Build And Run
|
|
|
|
|
|
|
|
|
|
From the repository root:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
make haskell-build
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Run the Haskell executable that calls Rust:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
make haskell-run
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Run the Rust executable that calls the Haskell foreign library:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
make rust-calls-haskell
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You can also run the commands manually:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
cargo build --lib
|
|
|
|
|
cabal build --project-file=haskell/cabal.project all
|
|
|
|
|
cabal run --project-file=haskell/cabal.project haskell-calls-rust -- Ada 7 5
|
|
|
|
|
cargo run -- rust-calls-haskell Ada 7 5
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## What This Demonstrates
|
|
|
|
|
|
|
|
|
|
- The boundary must stay C-shaped. Rich Rust and Haskell types do not cross directly.
|
|
|
|
|
- Strings need explicit ownership rules. Each side exports its own free function.
|
2026-03-27 09:20:31 +01:00
|
|
|
- Borrowed slices and owned returned buffers are different patterns and must be modeled differently.
|
|
|
|
|
- Raw byte buffers should be treated separately from C strings.
|
2026-03-24 14:34:09 +01:00
|
|
|
- Struct layout must be mirrored carefully on both sides.
|
|
|
|
|
- Rust calling Haskell is the harder direction because it must initialize and shut down the GHC runtime correctly.
|
|
|
|
|
- Build order is part of the design. Haskell links against the Rust static library, and Rust loads the Haskell foreign library after Cabal builds it.
|