2026-03-27 09:20:31 +01:00

74 lines
2.5 KiB
Markdown

# 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
- borrowed slices as `ptr + len`
- owned returned buffers as `ptr + len + cap`
- 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.
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
## 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.
- Borrowed slices and owned returned buffers are different patterns and must be modeled differently.
- Raw byte buffers should be treated separately from C strings.
- 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.