cabal-pack
A tool that helps you to turn in one command a Rust crate into a Haskell Cabal library!
To generate bindings, you need to annotate the Rust function you want to expose with hs-bindgen
macro.
Getting started
Here a little screencast demonstrating how it works (commands walkthrough are just pasted below):
N.B. You need in your
$PATH
a working Rust and Haskell environment, if you use Nix you can just enter:nix-shell -p cabal-install ghc cargo rustc
Welcome in this little cabal-pack
/ hs-bindgen
demo
Let's start by creating a dumb Rust library!
$ cargo new --lib greetings
Created library `greetings` package
$ tree greetings
greetings
├── Cargo.toml
└── src
└── lib.rs
1 directory, 2 files
$ cd greetings
Add hs-bindgen
to the dependencies list:
$ cargo add hs-bindgen
Updating crates.io index
Adding hs-bindgen v0.6.0 to dependencies.
Features:
+ antlion
And use it to decorate the function we want to expose:
src/lib.rs
:
use hs_bindgen::*;
#[hs_bindgen]
fn hello(name: &str) {
println!("Hello, {name}!");
}
$ cargo build Compiling proc-macro2 v1.0.47
Compiling quote v1.0.21
Compiling unicode-ident v1.0.5
Compiling syn v1.0.103
Compiling serde_derive v1.0.147
Compiling thiserror v1.0.37
Compiling serde v1.0.147
Compiling semver v1.0.14
Compiling antlion v0.3.0
Compiling lazy_static v1.4.0
Compiling thiserror-impl v1.0.37
Compiling displaydoc v0.2.3
Compiling hs-bindgen-traits v0.6.1
Compiling toml v0.5.9
Compiling hs-bindgen-derive v0.6.1
Compiling hs-bindgen v0.6.1
Compiling greetings v0.1.0 (/Users/yvan/demo/greetings)
error: custom attribute panicked
--> src/lib.rs:3:1
|
3 | #[hs_bindgen]
| ^^^^^^^^^^^^^
|
= help: message: fail to read content of `.hsbindgen` configuration file
n.b. you have to run the command `cabal-pack` to generate it: Os { code: 2, kind: NotFound, message: "No such file or directory" }
error: could not compile `greetings` due to previous error
So, we will use cabal-pack
to check our setup and generate Cabal files:
$ cargo install cabal-pack
Updating crates.io index
Ignored package `cabal-pack v0.6.0` is already installed, use --force to override
$ cabal-pack
Error: Your `Cargo.toml` file should contain a [lib] section with a `crate-type` field
that contains either `staticlib` or `cdylib` value, e.g.:
[lib]
crate-type = ["staticlib"]
N.B. if you're a Nix user, rather than rely on impure
cargo install
fell free to justnix run github:yvan-sraka/cabal-pack
Right, we edit the Cargo.toml
accordingly:
Cargo.toml
:
[package]
name = "greetings"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hs-bindgen = "0.6"
[lib]
crate-type = ["staticlib"]
$ cabal-pack
Cabal files generated!
**********************
You should now be able to compile your library with `cabal build` and should
add `hs-bindgen` to your crate dependencies list and decorate the Rust function
you want to expose with `#[hs_bindgen]` attribute macro.
$ ls
Cargo.lock Cargo.toml Setup.lhs greetings.cabal src target
$ cargo build
Compiling hs-bindgen v0.6.1
Compiling greetings v0.1.0 (/Users/yvan/demo/greetings)
Finished dev [unoptimized + debuginfo] target(s) in 0.55s
$ cabal build
Build profile: -w ghc-9.0.2 -O1
In order, the following will be built (use -v for more details):
- greetings-0.1.0 (lib:greetings) (first run)
[1 of 1] Compiling Main ( omitted ... )
Linking /Users/yvan/demo/dist-newstyle/build/aarch64-osx/ghc-9.0.2/greetings-0.1.0/setup/setup ...
Configuring greetings-0.1.0...
Preprocessing library for greetings-0.1.0..
Building library for greetings-0.1.0..
[1 of 1] Compiling Greetings ( src/Greetings.hs, omitted ... )
It works! And so cargo build
too if you just want to use the library in a Rust project!
Now let's try to use our freshly generated library in an Haskell app
$ cd ..
$ cabal init --non-interactive test
[Log] Guessing dependencies...
[Log] Using cabal specification: 3.8
[Warning] unknown license type, you must put a copy in LICENSE yourself.
[Log] Creating fresh file CHANGELOG.md...
[Log] Creating fresh directory ./app...
[Log] Creating fresh file app/Main.hs...
[Log] Creating fresh file test.cabal...
[Warning] No synopsis given. You should edit the .cabal file and add one.
[Info] You may want to edit the .cabal file and add a Description field.
$ tree test
test
├── app
│ └── Main.hs
├── CHANGELOG.md
└── test.cabal
1 directory, 3 files
We create a cabal.project
(equivalent to cargo workspace) to perform a local test without having to upload greetings
on hackage:
cabal.project
:
packages: ./greetings ./test
We edit test.cabal
to make it depends on greetings
library:
test/test.cabal
(content partially omitted):
executable test
-- Other library packages from which modules are imported.
build-depends: base, greetings
We write a minimalist main
function that will make call hello
from Greetings
module
test/app/Main.hs
:
module Main where
import Foreign.C.String
import Greetings
main :: IO ()
main = withCString "Rust 🦀" hello
Let's check if everything works as expected:
$ cabal run test
Build profile: -w ghc-9.0.2 -O1
In order, the following will be built (use -v for more details):
- test-0.1.0.0 (exe:test) (first run)
Configuring executable 'test' for test-0.1.0.0..
Preprocessing executable 'test' for test-0.1.0.0..
Building executable 'test' for test-0.1.0.0..
[1 of 1] Compiling Main ( app/Main.hs, omitted ... )
Linking /Users/yvan/demo/dist-newstyle/build/aarch64-osx/ghc-9.0.2/test-0.1.0.0/x/test/build/test/test ...
Hello, Rust 🦀!
That's all folks! Happy hacking
Nix support
The --enable-nix
CLI arg makes cabal-pack
generate a haskell.nix / naersk based flake.nix
rather than the Setup.lhs
.
N.B. when first working with
hs-bindgen
and Nix flakes, checking ifCargo.lock
isn't in.gitignore
and runningcargo build
andgit add --all
beforenix build
, will save you a lot of pain😉
Acknowledgments
cabal-pack
was heavily inspired by other interoperability initiatives, as wasm-pack
and Maturin
.
This project was part of a work assignment as an IOG contractor.