Joseph Mullins

Programmer | Security | Automation

joseph@ropeney.com

Making Ruby faster with Rust and Fiddle

Back

Why?

Easy! We know compiled languages are generally quicker, and we want to take use of that! Rust is a perfect language for finding hotspots in your ruby code that is slow and writing them in a safe compiled language.

FFI/Fiddle

Fiddle is a ruby module for wrapping libffi. libffi is a library that provides a interface for calling code in one language from another language.

A simple Rust program, hello world!

# helloworld.rs

#[no_mangle]
pub extern "C" fn hello_world() {
    println!("Hello from rust!");
}

We want to compile it as a dynamic library, so we use rustc helloworld.rs --crate-type=dylib.

Saying "Hello" from Rust in Ruby

#!/usr/bin/env ruby require "fiddle" require "fiddle/import" module RustHello extend Fiddle::Importer dlload "./libhelloworld.dylib" extern "void hello_world()" end RustHello.hello_world()

If all went well you should get Hello from rust!

Passing a value to Rust and getting the power back

# function_example.rs

#[no_mangle]
pub extern "C" fn power(base: u32, exponent: u32) -> u32 {
    base.pow(exponent)
}

Again, we compile it the same way rustc function_example.rs --crate-type=dylib.

The Ruby code

require "fiddle"
require "fiddle/import"

module RustFunction
  extend Fiddle::Importer

  dlload "./libfunction_example.dylib"

  extern "int power(int, int)"
end

p RustFunction.power(5, 3)

If all went well you should get 125

Persisting data, and passing objects to calculate the area of a rectangle

# object_example.rs

#[derive(Clone)]
pub struct Rectangle { x: usize, y: usize }

impl Rectangle {
    pub fn area(&self) -> usize {
        self.x * self.y
    }
}

#[no_mangle]
pub extern "C" fn make_rectangle(x: usize, y: usize) -> Box {
    Box::new(Rectangle { x: x, y: y })
}

#[no_mangle]
pub extern "C" fn rectangle_area(rectangle: &Rectangle) -> usize {
    rectangle.area()
}

 

 

After learning some more about rusts ownership and lifetime system, I now believe the following code is better since it does a move instead of a clone.

use std::f64;

pub struct Point { x: isize, y: isize}

struct Line<'a>  { p1: &'a Point, p2: &'a Point }

impl<'a> Line<'a> {
  pub fn length(&self) -> f64 {
    let xdiff = self.p1.x - self.p2.x;
    let ydiff = self.p1.y - self.p2.y;
    println!("{} {}", self.p1.x, self.p2.x);
    println!("{}", (((xdiff as f64).powi(2) + (ydiff as f64).powi(2)) as f64).sqrt());
    (((xdiff as f64).powi(2) + (ydiff as f64).powi(2)) as f64).sqrt()
  }
}

#[no_mangle]
pub extern "C" fn make_point(x: isize, y: isize) -> Box {
    println!("making point: {} {}", x, y);
    Box::new(Point { x: x, y: y })
}

#[no_mangle]
pub extern "C" fn get_distance(p1: &Point, p2: &Point) -> f64 {
    println!("getting distance: {}", p1.x);
    Line { p1: p1, p2: p2 }.length()
}

Then compile rustc object_example.rs --crate-type=dylib.

The ruby code

require "fiddle"
require "fiddle/import"

module RustObject
  extend Fiddle::Importer
  dlload  "./libobject_example.dylib"

  extern "Box* make_rectangle(int, int)"
  extern "int rectangle_area(Rectangle*)"
end

box1 = RustObject.make_rectangle(5,5)
p box1
p RustObject.rectangle_area(box1)

And the output should be

#<Fiddle::Pointer:0x007f9272091f70 ptr=0x007f92720920f0 size=0 free=0x00000000000000>
25

Enjoy!

Hopefully these examples come in use to you! Rust definitely looks like the language of choice to tie into ruby, giving quick entry to writing secure production ready code; with great abstractions. Feel free to ask any questions or leave any feedback below.

Comments