Rust-Curryrs: Bridge the gap between Haskell and Rust

Curryrs

Curryrs (a play on the name of Haskell Curry, rs for Rust libraries, and it's pronunciation couriers) is a library for providing easy to use bindings between Rust and Haskell Code. Given the type safety inherent in both languages Curryrs seeks to bridge the gap between the two languages by providing an interface between the two that makes writing FFI code a relatively painless experience.

This library has only been tested with GHC 8.0.1 and Rust Stable. To run the test suite you'll need gcc.

Project Status

This library used to not work due to some linking issues. It's not working still right now in it's current state but a fix is on the way see here for more details!

Installation

In your Rust project in Cargo.toml:

[dependencies]
curryrs = "^0.2.0"

In your Haskell project in it's cabal file:

build-depends: curryrs >= 0.2.0 < 0.3.0

How to use Curryrs

Each library contains a module for the FFI types and one for conversion to and from types that need extra work to do so. Right now this conversion module only affects the Boolean type, however work in the future of this module will likely include structs and other more complicated data structures.

Rust in Haskell

If you want to create functions that export to Haskell from Rust do the following:

#[macro_use]
extern crate curryrs;

use curryrs::types::*;

// Place each function you want exported into the safe_ffi! macro and it will
// export each one and place the pub extern for you!
safe_ffi! (

	fn double(x: I32) -> I32 {
		2 * x
	}

	fn square(x: U64) -> U64 {
		x * x
	}

	fn cube(x: I64) -> I64 {
		x * x * x
	}

);

Currently this macro doesn't work if unsafe is put in as part of the fn header. There are two macros: safe_ffi! and unsafe_ffi!. While they are both the same for now when a binary is created to help auto generate the bindings it will create unsafe or safe imports to Haskell depending on which macros the functions are in. The recommended use case is safe_ffi! for most of what you'll need.

Then in your Haskell program:

import Curryrs.Types

foreign import ccall "double" double :: I64 -> I64
foreign import ccall "square" square :: I64 -> I64
foreign import ccall "cube" cube :: I64 -> I64

quadruple :: I64 -> I64
quadruple x = double $ double x

fourthPower :: I64 -> I64
fourthPower x = square $ square x

ninthPower :: I64 -> I64
ninthPower x = cube $ cube x

Haskell in Rust

To run your Haskell code in Rust do the following steps:

First write and export the code you want for Haskell and use the Curryrs.Types module to have FFI compatible types.

import Curryrs.Types

foreign export ccall fourth :: I64 -> I64
foreign export ccall fifth :: I64 -> I64
foreign export ccall sixth :: I64 -> I64

fourth :: I64 -> I64
fourth x = x * x * x * x

fifth :: I64 -> I64
fithh x = x * x * x * x * x

sixth :: I64 -> I64
sixth x = x * x * x * x * x * x

In your cabal file add the following lines:

other-extensions: ForeignFunctionInterface

-- It should end with .so if you're on Linux, .dylib for Mac, and
-- .dll for Windows
ghc-options: -dynamic -fPIC -shared -o lib{your_library_name_here}.so

Now in your Cargo.toml file add the following under package:

build = "build.rs"

Then in your build.rs file:

fn main() {
  println!("cargo:rustc-link-search=native={path_to_your_haskell_library_directory}");
  println!("cargo:rustc-link-lib=native={library_name_w/o_lib_and_extension}");
}

This links your Haskell library in at compilation. Now for the actual code itself:

extern crate curryrs;
use curryrs::hsrt::{start,stop};
use curryrs::types::I64;

extern {
  pub fn fourth(x: I64) -> I64;
  pub fn fifth(x: I64) -> I64;
  pub fn sixth(x: I64) -> I64;
}

fn main() {
  // Input is whatever you want to pass to argv whenever
  // you start the Haskell Runtime. You need to start it
  // or calls to Haskell code will fail.
  start("Haskell Functions".to_string());

  println!("2^4 is: {}", unsafe{fourth(2)});
  println!("2^5 is: {}", unsafe{fifth(2)});
  println!("2^6 is: {}", unsafe{sixth(2)});

  // You need to make sure the runtime is stopped
  // otherwise you'll have undefined behavior
  // and wasted resources.
  stop();
}

This makes it easy to do without needing to muck around with linking the right libraries and you're easily able to call the runtime you want.

The library also allows you to choose which version of the Haskell Runtime you want to use. By default it uses the non-threaded version. You can choose which one you want with a feature flag in Cargo.toml

[dependencies]
# If you need the threaded runtime put this:
curryrs = { version = "^0.2.0", features = "threaded" }

# If you need the threaded runtime w/ logging put this:
curryrs = { version = "^0.2.0", features = "threaded_l" }

# If you need the threaded runtime w/ debug output put this:
curryrs = { version = "^0.2.0", features = "threaded_debug" }

Bug Reports

If you encounter errors of any sort please take a look in the issue tracker first. If your error is already there or has been closed before take a look at how it was solved or contribute to the open bug by explaining what has happened while using the library. Duplicates will be marked and closed.

Contributing

See CONTRIBUTING.md for more information.

Tests

See TESTS.md for more information.

Changelog

To see a list of changes between version take a look at CHANGELOG.md for more information.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Comments

  • Rust Inline
    Rust Inline

    Oct 19, 2016

    Someone was curious if they could write Rust inline in Haskell using quasi quotes to do it. I think this would be cool but isn't a priority right now.

    Reply
  • Setup CI testing
    Setup CI testing

    Oct 23, 2016

    It would be good to test code changes with a CI system automatically that can setup and run the tests. Preferably it would test a minimum version and all versions after it in terms of rustc and GHC versions so users can know the minimum the library supports..

    Reply
  • Get curryrs on Stackage
    Get curryrs on Stackage

    Oct 25, 2016

    Curryrs is available on Hackage but some might prefer the stability of Stackage and it makes it easier to setup rather than having to put Curryrs into the extra-deps part of the stack.yaml file for a project.

    Reply
  • Implement a Haskell trait
    Implement a Haskell trait

    Oct 26, 2016

    Not sure if this is feasible but it might be worth looking into.

    https://www.reddit.com/r/rust/comments/59fm88/curryrs_020_released_it_can_now_call_the_haskell/d989jc7/

    Reply
  • Exposing CString like this is prone to improper ownership management
    Exposing CString like this is prone to improper ownership management

    Nov 15, 2016

    As far as i can tell, the description of how to handle complex data structures in #5 is proper. CString should count as "complex", as it wraps some byte array, yet it is exposed like other, more trivial types in the current interface. I.e. one might easily write

    safe_ffi! (
      fn testString() -> CString {
        CString::new("hello").unwrap()
      }
    );
    

    which, under my current understanding, is rather unsafe.

    The easiest safe interface I can currently think of involves two wrapper types: RustCString and HaskellCString, where the name indicates which language's allocator is used for the underlying data. Safety could be ensured by only exposing a native constructor for each type in the respective language. (Technically only one such type would suffice by making a static choice to do String allocations in one language's allocator only. But the implementations would be symmetrical anyways..)

    Reply
  • Let Haskell tell rustc which libs it needs.
    Let Haskell tell rustc which libs it needs.

    Jan 1, 2017

    (Note: I don't expect this to be merged in its current state. There are a few problems which I am looking for suggestions on.)

    Description

    A custom Setup.hs is used on the Haskell side which figures out what libs are needed and outputs that as "cargo:" keys. A custom build.rs is used on the Rust side to pass those keys onto Cargo.

    Use case

    Suppose you have a Rust project and you would like to use some Haskell functions in it. You have the Haskell part of your project managed by Cabal and you want an easy way to link to it from Cargo. Here's what to do:

    To the Cabal part:

    1. Change the build-type to "Custom"
    2. Add "curryrs" to your setup-depends
    3. Change your "Setup.hs" to this:
    import Curryrs.Build
    main = curryrsMain
    

    To the Cargo part:

    1. Add "curryrs" to your build-dependencies
    2. In your build script, add a call to curryrs::build::link_package

    Then just run "cargo build". It will build the Haskell package and link the required libs for you.

    Currently, the shared lib versions are linked for all except for the package itself.

    Problems

    • No Stack support
    • No support for choosing an rts
    • Haskell's "curryrs" now depends on "Cabal" (Would it be better to put Curryrs.Build in a separate package?)
    • "htest" now depends on "curryrs"
    Reply
  • Add new bindgen tool for Haskell and Rust
    Add new bindgen tool for Haskell and Rust

    Oct 25, 2016

    This adds a new binary tool users can use to automatically generate imports for Haskell or Rust libraries without having to parse and write it themselves.

    Right now this only supports Haskell to Rust and doesn't account for things like improper Types and assumes valid syntax.

    Example:

    foreign export ccall hello :: I64 -> I64 -> I32
    foreign export ccall goodbye :: I64 -> I32
    

    gets turned into the output of

    extern {
       pub fn hello(a: I64, b: I64) -> I32;
       pub fn goodbye(a: I64) -> I32;
    }
    

    Note this doesn't include linking or anything like that but it's a good start for the tool.

    @nanotech give it a whirl and let me know what you think. in the hrgen directory call:

    cargo run -- -h Test.hs
    

    To have it run.

    Reply
  • Safer and more convenient Haskell runtime start and stop
    Safer and more convenient Haskell runtime start and stop

    Oct 30, 2016

    • Let hsrt::start be called any number of times, ignoring subsequent calls.
    • Automatically call hs_exit at program exit.
    • Panic as a diagnostic if hsrt::stop is called manually more than once.

    hsrt::stop doesn't technically need to panic, it could just ignore subsequent calls like hsrt::start, but I think that if you are going to call it manually, you should know exactly when all your exported Haskell functions become erroneous to call.

    The tests are still in one #[test] function as Rust's test runner runs them in parallel and htest doesn't use the threaded runtime. It would be nice if we could set the threaded feature for profile.test, although I don't think that's currently possible with Cargo.

    The Windows implementation for argument passing is correct as far as I can tell from the GHC sources, but not actually tested on Windows.

    Provides a piece of #14.

    Reply
  • Call hs_init from Rust directly
    Call hs_init from Rust directly

    Oct 16, 2016

    Some remaining issues:

    Nice work figuring this out, by the way. I've always had to resort to a Haskell -> Other Language -> Haskell inversion of control to avoid building the executable without GHC.

    Reply
  • Failing Tests
    Failing Tests

    Jun 23, 2017

    It seems like there's linkage problems with using rustup now until this gets fixed it will cause issues elsewhere and might not let curryrs work at all for that matter.

    bug 
    Reply
  • Haskell type parser using combine
    Haskell type parser using combine

    Oct 26, 2016

    It should be able to parse any Haskell 2010 type except for type class constraints and type operators. This isn't connected to the generator yet, and doesn't parse the foreign export ccall part yet. The parser is in a separate crate since it's a bit slow to compile.

    Reply
  • Add Haskell Runtime interface
    Add Haskell Runtime interface

    Oct 23, 2016

    This commit does quite a number of things but the important part is that it adds two functions that allow one to start and stop the Haskell Runtime from one's code without needing to deal with all of the library linking. The build script now goes to the ghc library directory where the code is located and finds all of the .so files and links against them to avoid dependency issues and making it easier to deal with other Haskell code in the future. This means the user doesn't need to worry about linking when it comes to getting the right Haskell libraries involved beyond their own library they've created. They also just need to use the library to start and stop the runtime without worrying about getting the right C code types lined up just right.

    Reply