├── .gitignore ├── README.md ├── Rakefile ├── interop.rb ├── rust_interop.rb └── src └── interop.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruby-rust-interop 2 | Showcasing the calling of number-based functions from Rust in a Ruby runtime 3 | 4 | I hope these examples will help you set up Ruby with Rust! 5 | 6 | All these examples were tested on Windows with Ruby 2.1.6 and Rust 1.0.0-beta.4 7 | 8 | If you clone the repo, make sure you have Ruby and Rust installed on your machine and run 9 | 10 | rake 11 | 12 | By default, this will compile the `src/interop.rs` file and run `interop.rb` to display the result of some number passing. 13 | 14 | Ruby's Fiddle library made it super easy, and [Yehuda Katz's blog here](http://blog.skylight.io/bending-the-curve-writing-safe-fast-native-gems-with-rust/) helped me get set up so thanks for that! 15 | 16 | I hope you enjoy the examples, and don't be afraid to ask me any questions. 17 | 18 | ## To Be Going On With 19 | 20 | You need 3 ingredients to get this working: 21 | 22 | ###Step 1 23 | 24 | write a function the right way in Rust (read at the bottom for a common problem on Windows and a simple workaround) 25 | 26 | ```rust 27 | #[no_mangle] 28 | pub extern "C" fn fn_name() {} 29 | ``` 30 | 31 | ###Step 2 32 | compile that function the right way 33 | 34 | ``` 35 | > rustc my_file.rs --crate-type=dylib 36 | ``` 37 | 38 | ###Step 3 39 | use Fiddle to create a method in Ruby from the C code 40 | 41 | ```ruby 42 | require 'fiddle' 43 | require 'fiddle/import' 44 | 45 | module RustFunctions 46 | extend Fiddle::Importer 47 | dlload "./my_file.dll" 48 | extern "void fn_name()" 49 | end 50 | ``` 51 | ###Step 4 52 | Now you can use RustFunctions.fn_name anywhere in your ruby code! 53 | 54 | Of course, to add parameters and return values, you have to know how to translate between Rust's types and C's types. 55 | 56 | ```rust 57 | #[no_mangle] 58 | pub extern "C" fn fn_name(x: i32) -> i32 { 59 | println!("You passed in {}", x); 60 | x + 1 61 | } 62 | ``` 63 | 64 | Compilation will be the same (you can see why I made a rakefile for that). 65 | 66 | And your Fiddle code will require the types: 67 | 68 | ```ruby 69 | require 'fiddle' 70 | require 'fiddle/import' 71 | 72 | module RustFunctions 73 | extend Fiddle::Importer 74 | dlload "./my_file.dll" 75 | extern "int fn_name(int)" 76 | end 77 | ``` 78 | 79 | And now, running RustFunctions.fn_name(3) will return 4. Hooray! 80 | 81 | Note to Windows users: there is a [known compiler bug](https://github.com/rust-lang/rust/issues/18807#issuecomment-102177511) for compiling dynamic libraries on Windows. At the bottom of that page I share an easy workaround. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :build do 2 | puts "\nBuilding dynamic libraries...\n\n" 3 | if system('rustc src/interop.rs --crate-type=dylib -A overflowing-literals') 4 | else 5 | puts "\n\tSomething went wrong during compilation. Please" 6 | puts "\tcheck your .rs files for errors.\n" 7 | $totally_bad_global_for_build = false 8 | end 9 | end 10 | 11 | task :run do 12 | load 'interop.rb' 13 | end 14 | 15 | task :build_and_run do 16 | $totally_bad_global_for_build = true 17 | Rake::Task['build'].execute 18 | if $totally_bad_global_for_build 19 | Rake::Task['run'].execute 20 | end 21 | end 22 | 23 | task :default do 24 | Rake::Task['build_and_run'].execute 25 | end -------------------------------------------------------------------------------- /interop.rb: -------------------------------------------------------------------------------- 1 | require_relative 'rust_interop' 2 | 3 | # All code here done with 4 | # ruby 2.1.6p336 (2015-04-13 revision 50298) [x64-mingw32] 5 | # rustc 1.0.0-beta.4 (850151a75 2015-04-30) (built 2015-05-01) 6 | 7 | # compiling: rustc src/interop.rs --crate-type=dylib -A overflowing-literals 8 | # ^^^^^^^^^^^^^^^^^^^^^^ (this squelches integer overflow warnings) 9 | # 10 | # Or just run rake. it will build and run by default. 11 | 12 | puts "Quick void -> void funciton just to make sure things are okay" 13 | puts 14 | RustInterop.void_test 15 | 16 | puts 17 | puts "Now we'll grab numbers from Rust and use them in Ruby" 18 | puts "You could put them into variables, but here we'll just" 19 | puts "print them out." 20 | puts 21 | puts RustInterop.i8_return_test # -128..127 22 | puts RustInterop.u8_return_test # 0..255 23 | puts RustInterop.i16_return_test # -32768..32767 24 | puts RustInterop.u16_return_test # 0..65535 25 | puts RustInterop.i32_return_test # -2147483648..2147483647 Bignum territory 26 | puts RustInterop.u32_return_test # 0..4294967295 27 | puts RustInterop.i64_return_test # -9223372036854775808..9223372036854775808 28 | puts RustInterop.u64_return_test # 0..18446744073709551615 29 | puts 30 | puts "Now for passing things to Rust..." 31 | puts 32 | 33 | # Even though you can pass values up to 2**31-1 (signed) and 2**32-1 (unsigned), 34 | # Rust will overflow them appropriately. For the 64 bit numbers, you can pass 35 | # in bigger numbers. In each, I'm passing in the maximum value you can pass 36 | # from Ruby, and showing the range it will overflow into. 37 | RustInterop.i8_pass_test(2**31-1) # -128..127 38 | RustInterop.u8_pass_test(2**32-1) # 0..255 39 | RustInterop.i16_pass_test(2**31-1) # -32768..32767 40 | RustInterop.u16_pass_test(2**32-1) # 0..65535 41 | RustInterop.i32_pass_test(2**31-1) # -2147483648..2147483647 42 | RustInterop.u32_pass_test(2**32-1) # 0..4294967295 43 | RustInterop.i64_pass_test(2**63-1) # -9223372036854775808..9223372036854775808 44 | RustInterop.u64_pass_test(2**64-1) # 0..18446744073709551615 45 | 46 | puts 47 | puts "Can't do a number showcase without some floating!" 48 | puts 49 | #Ruby doesn't distinguish between 32bit and 64bit floats. They're all 64bit in Ruby. 50 | # So be careful when reasoning about the passing of 32bit floats back and forth. 51 | puts RustInterop.f32_return_test 52 | puts RustInterop.f64_return_test 53 | 54 | RustInterop.f32_pass_test(1.00000006) 55 | RustInterop.f64_pass_test(1.00000000000000015) -------------------------------------------------------------------------------- /rust_interop.rb: -------------------------------------------------------------------------------- 1 | require 'fiddle' 2 | require 'fiddle/import' 3 | 4 | module RustInterop 5 | extend Fiddle::Importer 6 | dlload "./interop.dll" 7 | extern "void void_test()" 8 | 9 | extern "char i8_return_test()" 10 | extern "unsigned char u8_return_test()" 11 | extern "short i16_return_test()" 12 | extern "unsigned short u16_return_test()" 13 | extern "int i32_return_test()" 14 | extern "unsigned int u32_return_test()" 15 | extern "long long i64_return_test()" 16 | extern "unsigned long long u64_return_test()" 17 | 18 | extern "void i8_pass_test(char)" 19 | extern "void u8_pass_test(unsigned char)" 20 | extern "void i16_pass_test(short)" 21 | extern "void u16_pass_test(unsigned short)" 22 | extern "void i32_pass_test(int)" 23 | extern "void u32_pass_test(unsigned int)" 24 | extern "void i64_pass_test(long long)" 25 | extern "void u64_pass_test(unsigned long long)" 26 | 27 | extern "float f32_return_test()" 28 | extern "double f64_return_test()" 29 | 30 | extern "void f32_pass_test(float)" 31 | extern "void f64_pass_test(double)" 32 | end -------------------------------------------------------------------------------- /src/interop.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub extern "C" fn void_test() { 3 | println!("Hello, world!"); 4 | } 5 | #[no_mangle] 6 | pub extern "C" fn i8_return_test() -> i8 { 7 | //this will overflow to -128 8 | 128 9 | } 10 | #[no_mangle] 11 | pub extern "C" fn u8_return_test() -> u8 { 12 | //this will overflow to 0 13 | 256 14 | } 15 | #[no_mangle] 16 | pub extern "C" fn i16_return_test() -> i16 { 17 | //this will overflow to -32768 18 | 32768 19 | } 20 | #[no_mangle] 21 | pub extern "C" fn u16_return_test() -> u16 { 22 | //this will overflow to 0 23 | 65536 24 | } 25 | #[no_mangle] 26 | pub extern "C" fn i32_return_test() -> i32 { 27 | //this will overflow to -2147483648 28 | 2147483648 29 | } 30 | #[no_mangle] 31 | pub extern "C" fn u32_return_test() -> u32 { 32 | //this will overflow to 0 33 | 4294967296 34 | } 35 | #[no_mangle] 36 | pub extern "C" fn i64_return_test() -> i64 { 37 | //this will overflow to -9223372036854775808 38 | 9223372036854775808 39 | } 40 | #[no_mangle] 41 | pub extern "C" fn u64_return_test() -> u64 { 42 | //this is the largest number literal you can type in Rust without the compiler throwing an error. 43 | 18446744073709551615 44 | } 45 | #[no_mangle] 46 | pub extern "C" fn i8_pass_test(x: i8) { 47 | println!("{}", x); 48 | } 49 | #[no_mangle] 50 | pub extern "C" fn u8_pass_test(x: u8) { 51 | println!("{}", x); 52 | } 53 | #[no_mangle] 54 | pub extern "C" fn i16_pass_test(x: i16) { 55 | println!("{}", x); 56 | } 57 | #[no_mangle] 58 | pub extern "C" fn u16_pass_test(x: u16) { 59 | println!("{}", x); 60 | } 61 | #[no_mangle] 62 | pub extern "C" fn i32_pass_test(x: i32) { 63 | println!("{}", x); 64 | } 65 | #[no_mangle] 66 | pub extern "C" fn u32_pass_test(x: u32) { 67 | println!("{}", x); 68 | } 69 | #[no_mangle] 70 | pub extern "C" fn i64_pass_test(x: i64) { 71 | println!("{}", x); 72 | } 73 | #[no_mangle] 74 | pub extern "C" fn u64_pass_test(x: u64) { 75 | println!("{}", x); 76 | } 77 | 78 | #[no_mangle] 79 | pub extern "C" fn f32_return_test() -> f32 { 80 | //just about the smallest value greater than 1.0 that will register. As 81 | // far as I can tell, you can add in any more zeros before the final "1" 82 | // (I went out to 2000 zeros) 83 | 1.000000059604644775390625000000000000000000000000000000000000000000001 84 | } 85 | #[no_mangle] 86 | pub extern "C" fn f64_return_test() -> f64 { 87 | //close to the smallest value greater than 1.0 that will register. 88 | // I'm sure it has the same weird rule from above, just divide the 89 | // decimal part by 2 if you want to play around with it. 90 | 1.0000000000000002220446049250313080847263336181640625 91 | } 92 | 93 | #[no_mangle] 94 | pub extern "C" fn f32_pass_test(x: f32) { 95 | //displaying beyond 7 decimal places shows unusable precision 96 | println!("{:.*}", 7, x); 97 | } 98 | #[no_mangle] 99 | pub extern "C" fn f64_pass_test(x: f64) { 100 | //displaying beyond 16 decimal places shows unusable precision 101 | println!("{:.*}", 16, x); 102 | } 103 | 104 | --------------------------------------------------------------------------------