2.5 KiB
2.5 KiB
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
i32slices sent across the boundary - owned
i32buffers 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:
make haskell-build
Run the Haskell executable that calls Rust:
make haskell-run
Run the Rust executable that calls the Haskell foreign library:
make rust-calls-haskell
You can also run the commands manually:
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.