├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── Rust.hs ├── Setup.hs ├── haskellrustdemo.cabal ├── lib.rs └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | dist/ 3 | Rust 4 | rust 5 | *_stub.h 6 | rust-c 7 | Rust-static 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rslib" 3 | version = "0.1.0" 4 | authors = [ "creichert07@gmail.com" ] 5 | 6 | [lib] 7 | name = "rslib" 8 | path = "lib.rs" 9 | crate-type = ["dylib", "staticlib"] 10 | 11 | [dependencies] 12 | libc = "0.1.7" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 18 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 20 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Compiles: 2 | # 3 | # 1) Dynamically linked C executable with embedded Rust library. 4 | # 5 | # 2) Dynamically linked Haskell executable with embedded Rust library 6 | # Dynamically linked at the C level against Rust library. 7 | # Embedded in this case simply meanse we are interacting 8 | # with Rust at the API level and not shelling out commands. 9 | # 10 | # 3) Statically linked Haskell executable with embedded Rust library 11 | # Statically linked at the C level and against Rust library. 12 | # 13 | # 4) Basic Cabal build (dynamically linked). 14 | # 15 | # *Makefile currently rebuilds even if the targets are up-to-date (intended)* 16 | 17 | 18 | CARGO = cargo 19 | CC = gcc 20 | GHC = ghc 21 | ECHO = echo 22 | CABAL = cabal 23 | 24 | 25 | build: cffi hsffi 26 | @$(ECHO) "Executables built" 27 | @$(ECHO) "\nTry 'make run'" 28 | 29 | 30 | run: cffi hsffi 31 | @$(ECHO) "\n\nexecuting C executable:" 32 | @$(ECHO) "=======================" 33 | @./rust-c 34 | @$(ECHO) "\n\nexecuting dynamically linked executable:" 35 | @$(ECHO) "========================================" 36 | @LD_LIBRARY_PATH=`pwd` ldd ./Rust | grep rslib 37 | @LD_LIBRARY_PATH=`pwd` ./Rust 38 | @$(ECHO) "\n\nexecuting statically linked executable:" 39 | @$(ECHO) "=======================================" 40 | @./Rust-static 41 | @$(ECHO) "\n\nexecuting cabal run:" 42 | @$(ECHO) "====================" 43 | @if hash $(CABAL) 2>/dev/null; then LD_LIBRARY_PATH=`pwd` $(CABAL) run; fi 44 | 45 | 46 | hsffi: cargo 47 | @$(ECHO) "Building using GHC command line" 48 | @$(GHC) --make Rust.hs -L. -lrslib -lpthread -o Rust 49 | @$(GHC) --make Rust.hs -fPIC -optl-static -optl-pthread -L. -lrslib -o Rust-static 50 | @$(ECHO) "Building using Cabal" 51 | @if hash $(CABAL) 2>/dev/null; then $(CABAL) build; fi 52 | 53 | 54 | cffi: cargo 55 | @$(CC) -c test.c 56 | @$(CC) -o rust-c test.o librslib.a -ldl -lpthread -lc -lm 57 | 58 | 59 | cargo: 60 | @[ -x ${RUSTC} ] || ($(ECHO) "ERROR: rust compiler (rustc) not found" && exit 1) 61 | @$(CARGO) build -v 62 | @ln -fs target/debug/*.so librslib.so 63 | @ln -fs target/debug/*.a librslib.a 64 | 65 | 66 | clean: 67 | @$(CARGO) clean 68 | @rm -f Rust_stub.h 69 | @rm -f Cargo.lock 70 | @rm -rf *~ *.hi *.o *.so target/ G* *.a 71 | @rm -f Rust rust-c Rust-static 72 | @if hash $(CABAL) 2>/dev/null; then $(CABAL) clean; fi 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Haskell-Rust foreign function interface demo 2 | 3 | This project shows how to use Rust from Haskell and Haskell from Rust. 4 | 5 | Last updated on **Tuesday, September 11, 2018** 6 | 7 | ## Build and Run 8 | 9 | Make sure the following executables are in your `PATH`: 10 | - `rustc` 11 | - `ghc` 12 | - `cargo` - used to show ffi linking using Cargo 13 | - `cabal` - used to show ffi linking a Haskell cabal project 14 | 15 | 16 | Then, build and run the project: 17 | 18 | $ make 19 | $ make run 20 | 21 | 22 | 23 | ## Resources 24 | - [[https://brson.github.io/2013/03/10/embedding-rust-in-ruby][Embedding Rust in Ruby]] 25 | - [[https://github.com/brson/rubyrustdemo][rubyrustdemo]] 26 | - [[http://doc.rust-lang.org/guide-ffi.html][Rust FFI Guide]] 27 | - [[http://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=ghc&lang2=rust][Benchmarks]] 28 | - [[https://pcwalton.github.io/blog/2013/04/18/performance-of-sequential-rust-programs][More Benchmarks]] 29 | - [[https://github.com/servo/servo][Servo]] 30 | - [[https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ffi-chap.html][GHC Manual - Chapter 10. Foreign function interface (FFI)]] 31 | -------------------------------------------------------------------------------- /Rust.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ForeignFunctionInterface #-} 2 | 3 | module Main where 4 | 5 | 6 | import Control.Concurrent (MVar, takeMVar, newMVar) 7 | import Foreign 8 | import Foreign.C 9 | 10 | 11 | type Callback = CInt -> IO () 12 | 13 | 14 | -- | rs_function function from Rust library 15 | foreign import ccall "rs_function" 16 | rs_function :: Int -> IO () 17 | 18 | 19 | -- | Register a callback within the rust library via rs_register 20 | foreign import ccall "rs_register" 21 | rs_register :: FunPtr Callback -> IO () 22 | 23 | 24 | -- | Create a callback wrapper to be called by the Rust library 25 | foreign import ccall "wrapper" 26 | makeCallback :: Callback -> IO (FunPtr Callback) 27 | 28 | 29 | 30 | callback :: MVar CInt -> CInt -> IO () 31 | callback mi i = print (msg i) 32 | >> takeMVar mi 33 | >>= print . statemsg 34 | where 35 | msg = (++) "Haskell-callback invoked with value: " . show 36 | statemsg = (++) " Haskell-callback carrying state: " . show 37 | 38 | 39 | 40 | main :: IO () 41 | main = do 42 | 43 | rs_function 0 44 | 45 | st <- newMVar 42 46 | rs_register =<< makeCallback (callback st) 47 | 48 | rs_function 1 49 | 50 | putStrLn "Haskell main done" 51 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /haskellrustdemo.cabal: -------------------------------------------------------------------------------- 1 | name: haskellrustdemo 2 | version: 0.1.0.0 3 | synopsis: Embed Rust in Haskell 4 | homepage: https://github.com/creichert/haskellrustdemo 5 | license: MIT 6 | license-file: LICENSE 7 | author: Christopher Reichert 8 | maintainer: creichert07@gmail.com 9 | copyright: (c) 2014-2018 Christopher Reichert 10 | category: FFI 11 | build-type: Simple 12 | extra-source-files: README.org test.c lib.rs 13 | tested-with: GHC == 7.8 14 | cabal-version: >=1.18 15 | 16 | executable haskellrustdemo 17 | main-is: Rust.hs 18 | other-extensions: ForeignFunctionInterface 19 | build-depends: base == 4.* 20 | default-language: Haskell2010 21 | -- cc-options: -fPIC -optl-static -optl-pthread 22 | extra-lib-dirs: . 23 | extra-libraries: rslib pthread 24 | -------------------------------------------------------------------------------- /lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | use self::libc::c_int; 3 | 4 | 5 | static mut HASKELL:Option = None; 6 | 7 | /* Register the callback */ 8 | #[no_mangle] 9 | pub fn rs_register(cb: extern fn(c_int)) { 10 | unsafe { 11 | HASKELL = Some(cb); 12 | println!("callback registered"); 13 | } 14 | } 15 | 16 | 17 | #[no_mangle] 18 | pub fn rs_function(val:c_int) { 19 | println!("triggered Rust function with: {}", val); 20 | 21 | if val != 42 { 22 | println!("life, the universe, and everything"); 23 | } 24 | 25 | unsafe { 26 | match HASKELL { 27 | Some(ref callback) => { 28 | println!("registered callback found"); 29 | (*callback)(100); 30 | }, 31 | _ => { 32 | println!("no callback has been registered"); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | 5 | void rs_function(int value); 6 | void rs_register(void(* value)(int)); 7 | 8 | 9 | void callback(int value) { 10 | printf("C callback invoked with value: %d\n", value); 11 | } 12 | 13 | 14 | int main(int argc, char *argv[]) { 15 | rs_function(0); 16 | rs_register(&callback); 17 | rs_function(42); 18 | 19 | return 0; 20 | } 21 | --------------------------------------------------------------------------------