Call Rust from Haskell with cargo-cabal and hs-bindgen

See https://sraka.xyz/posts/hs-bindgen-introduction.html.

For now, this is a shell-based workflow, rather than using Nix to build everything, i.e. `nix develop` works but `nix build` doesn't. And `cargo build` has to be called manually to create the C library, rather than `cabal` being clever enough to invoke it itself.

We ran `cargo cabal init` from the `rust` directory (`nix shell github:yvan-sraka/cargo-cabal`), which generated `hsbindgen.toml` (which we use), and `Setup.lhs` (which just added `extra-lib-dirs`, and with the wrong paths, so we dspecify those statically instead in `garnet.cabal`). We also follow its advice to use `staticlib`.

Also, after we ran the first `cargo build` (requiring a `mkdir rust/src` before it would run), we took the generated the Haskell file, and moved the main contents in to `Main.hs` manually.
This commit is contained in:
George Thomas 2026-02-18 15:49:08 +00:00
parent ff8826d758
commit 7bb41f6825
8 changed files with 250 additions and 14 deletions

View File

@ -63,10 +63,6 @@
rust-analyzer rust-analyzer
]; ];
}; };
packages = {
haskell = haskell.packages."garnet:exe:garnet";
rust = rust.buildPackage { src = rust.cleanCargoSource ./rust; };
};
} }
); );
} }

View File

@ -1,5 +1,8 @@
module Main (main) where module Main (main) where
import Foreign.C.String (CString, withCString)
foreign import ccall safe "__c_hello" hello :: CString -> IO ()
main :: IO () main :: IO ()
main = do main = withCString "Haskell" hello
putStrLn "Hello from Haskell"

View File

@ -33,5 +33,11 @@ executable garnet
-threaded -threaded
-rtsopts -rtsopts
-with-rtsopts=-N -with-rtsopts=-N
extra-lib-dirs:
-- TODO referring to parent triggers warning - maybe put Rust stuff in subdir
-- TODO bit weird to have both of these, but it's what the `cargo-cabal` setup script did
../rust/target/release
../rust/target/debug
extra-bundled-libraries: garnet_rs
build-depends: build-depends:
base >= 4.14, base >= 4.14,

214
rust/Cargo.lock generated
View File

@ -3,5 +3,217 @@
version = 4 version = 4
[[package]] [[package]]
name = "garnet" name = "antlion"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd743dc9b5cf465db1be79d28b4bfd7fa143d289546afeea5dc933551883e1f6"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
]
[[package]]
name = "garnet-rs"
version = "0.1.0" version = "0.1.0"
dependencies = [
"hs-bindgen",
]
[[package]]
name = "hs-bindgen"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9723a81f9f23c3e52b01ce72db20c7ab384c8618b1032eea04985260d10fc7"
dependencies = [
"hs-bindgen-attribute",
"hs-bindgen-traits",
]
[[package]]
name = "hs-bindgen-attribute"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57fc3cb0491f749fc50e7cf186c189b64af557edb9f78e36eb78bb9021d6624"
dependencies = [
"antlion",
"displaydoc",
"hs-bindgen-types",
"lazy_static",
"quote",
"rustc_version",
"semver 1.0.27",
"serde",
"syn 1.0.109",
"thiserror",
"toml",
]
[[package]]
name = "hs-bindgen-traits"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e92e839254ee5975299f64183e8346548c4ba5e4ee0253a05426891a6f135a6a"
[[package]]
name = "hs-bindgen-types"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2d986de5641ea4aa8025589d7243e403961fb0f5572c4dd4764a1bb4a01768c"
dependencies = [
"displaydoc",
"proc-macro2",
"quote",
"thiserror",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.116",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"

View File

@ -1,8 +1,11 @@
[package] [package]
name = "garnet" name = "garnet-rs"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[[bin]] [lib]
path = "main.rs" path = "lib.rs"
name = "garnet" crate-type = ["staticlib"]
[dependencies]
hs-bindgen = { version = "0.8", features = ["full"] }

13
rust/hsbindgen.toml Normal file
View File

@ -0,0 +1,13 @@
# Since the only `.cabal` format parser implementation and specification live
# in Cabal itself ... this deadly simple config file is used by `hs-bindgen`
# Rust crate to get needed data (like default exposed module name).
default = "GarnetRs"
# There is an unlikely future where instead we have a Rust `.cabal` parser,
# that most likely would rely under the hood on a Haskell static lib wrapper
# of `Cabal.Parse` or https://hackage.haskell.org/package/Cabal-syntax library.
# But even in this case, it would be nice to know the `cargo-cabal` version that
# generated the `.cabal` file used.
version = "0.8.0"

6
rust/lib.rs Normal file
View File

@ -0,0 +1,6 @@
use hs_bindgen::*;
#[hs_bindgen(hello :: CString -> IO (()))]
fn hello(name: &str) {
println!("Hello from Rust, {name}!");
}

View File

@ -1,3 +0,0 @@
fn main() {
println!("Hello from Rust");
}