cabal-pack 0.5.0

A tool that helps you to turn in one command a Rust crate into a Haskell Cabal library
cabal-pack-0.5.0 is not a library.

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):

asciinema

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.5.0 to dependencies.

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.46
   Compiling unicode-ident v1.0.4
   Compiling quote v1.0.21
   Compiling syn v1.0.101
   Compiling serde_derive v1.0.145
   Compiling serde v1.0.145
   Compiling semver v1.0.14
   Compiling toml v0.5.9
   Compiling hs-bindgen v0.5.0
   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.5.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 `staticlib` value:

[lib]
crate-type = ["staticlib"]

N.B. if you're a Nix user, rather than rely on impure cargo install fell free to just nix 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.5.0"

[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.5.0
   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)
Preprocessing library for greetings-0.1.0..
Building library for greetings-0.1.0..
[1 of 1] Compiling Greetings        ( src/Greetings.hs, /Users/yvan/demo/greetings/dist-newstyle/build/aarch64-osx/ghc-9.0.2/greetings-0.1.0/build/Greetings.o, /Users/yvan/demo/greetings/dist-newstyle/build/aarch64-osx/ghc-9.0.2/greetings-0.1.0/build/Greetings.dyn_o )

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 = hello =<< newCString "Rust"

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, /Users/yvan/demo/dist-newstyle/build/aarch64-osx/ghc-9.0.2/test-0.1.0.0/x/test/build/test/test-tmp/Main.o )
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 if Cargo.lock isn't in .gitignore and running cargo build and git add --all before nix build, will save you a lot of pain 😉

Acknowledgments

⚠️ This is still a working experiment, not yet production ready.

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.