Stoecki
Stoecki

Reputation: 597

Is it possible to call Rust's struct fields directly from Ruby code without implementing extern "C" getter to the corresponding fields

I am thinking about writing a Ruby gem with Rust. Lets assume I want to create some structs in Rust which are returned to the Ruby code similar to the example here. While getting the Point struct to my Ruby code, I would like to call its attributes directly. Currently I would have to do something like that:

point.rb:

require "fiddle"
require "fiddle/import"

module RustPoint
  extend Fiddle::Importer

  dlload "./libmain.dylib"

  extern "Point* make_point(int, int)"
  extern "double get_distance(Point*, Point*)"
  extern "int y(Point*)"
  extern "int x(Point*)"

end

main.rs:

use std::num::pow;

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

#[no_mangle]
pub extern "C" fn make_point(x: int, y: int) -> Box<Point> {
  box Point { x: x, y: y }
}

#[no_mangle]
pub extern "C" fn x(p: &Point) -> int {
  p.x
}

#[no_mangle]
pub extern "C" fn y(p: &Point) -> int {
  p.y
}

and use this in Ruby:

point = RustPoint::make_point(0, 42)
# To get x:
x = RustPoint::x(point)

to get an x value. I would prefer something like:

point = RustPoint::make_point(0, 42)
# To get x:
x = point.x

Does anyone know a library or a way to get this implemented easier. I think it would be much nicer if i wouldn't see a different regarding the point object from ruby side. i should not make a difference weather this is a C extension, a Ruby object or written in Rust.

Edit: I want the Rust code to behave like a native extension. So the returned struct should be callable from Ruby side similar to a C struct using ruby objects as values. Of course a library would be nessessary to handle the ruby objects in rust code.

Upvotes: 1

Views: 267

Answers (2)

Levans
Levans

Reputation: 15002

Rust provides a native C interface for struct as well. If you define your struct like this:

#[repr(C)]
pub struct Point {
    pub x: i32,
    pub y: i32
}

It will behave like the C struct

struct Point
{
    int32_t x;
    int32_t y;
}

You may then use it in Ruby like any other C struct.

I recommend using the fixed size int types rather than plain int, because you have no real guaranty Rust's int are the same size as C's int. If you really need to use it, you should probably use libc::c_int.

Upvotes: 0

Patrick Oscity
Patrick Oscity

Reputation: 54684

You could wrap the whole thing in a custom delegator:

class RustDelegator
  attr_accessor :__delegate_class__, :__delegate__

  def method_missing(method_name, *arguments, &block)
    __delegate_class__.public_send(method_name, *__rust_arguments__(arguments), &block)
  end

  def respond_to_missing(name, include_private = false)
    __delegate_class__.respond_to?(name, include_private)
  end

  private

  def __rust_arguments__(arguments)
    arguments.unshift(__delegate__)
  end
end

class Point < RustDelegator
  def initialize(x, y)
    self.__delegate_class__ = RustPoint
    self.__delegate__ = RustPoint::make_point(0, 42)
  end
end

p = Point.new(0, 42)
#=> #<Point:0x007fb4a4b5b9d0 @__delegate__=[0, 42], @__delegate_class__=RustPoint>

p.x
#=> 0

p.y
#=> 42

Upvotes: 1

Related Questions