~tim/rust-ruby-wrap-data

ba21d92f8c2bc07c95d66b25a6190c672e9d885c — Tim Morgan 2 years ago 20b8b3b
Separate the meat of the code into a module
3 files changed, 96 insertions(+), 110 deletions(-)

M src/lib.rs
A src/wrap.rs
M test.rb
M src/lib.rs => src/lib.rs +43 -105
@@ 1,133 1,71 @@
extern crate libc;
extern crate ruby_sys;

#[derive(Debug)]
enum MyValue {
    Vec(Box<Vec<MyValue>>),
    Str(String),
}
mod wrap;

use ruby_sys::{array::{rb_ary_entry, rb_ary_len, rb_ary_new, rb_ary_push},
               class::{rb_define_class, rb_define_method}, rb_cObject,
               string::{rb_str_new_cstr, rb_string_value_cstr},
               types::{c_char, c_void, CallbackPtr, RBasic, Value},
               value::{RubySpecialConsts::Nil, ValueType}};
use ruby_sys::{class::{rb_define_class, rb_define_method}, rb_cObject,
               types::{CallbackPtr, Value}};

use std::ffi::CString;
use std::mem;
use std::ffi::{CStr, CString};

#[repr(C)]
struct RData {
    basic: RBasic,
    dmark: Option<extern "C" fn(*mut c_void)>,
    dfree: Option<extern "C" fn(*mut c_void)>,
    pub data: *mut c_void,
}

extern "C" {
    pub fn rb_define_alloc_func(klass: Value, func: extern "C" fn(Value) -> Value);
    pub fn rb_data_object_wrap(
        klass: Value,
        datap: *mut c_void,
        mark: Option<extern "C" fn(*mut c_void)>,
        free: Option<extern "C" fn(*mut c_void)>,
    ) -> Value;
}

#[allow(dead_code)]
const RB_NIL: Value = Value {
    value: Nil as usize,
};

fn c_str(string: &str) -> CString {
    CString::new(string).unwrap()
}

fn c_str_ptr(string: &str) -> *const c_char {
    c_str(string).into_raw()
}

pub extern "C" fn free(data: *mut c_void) {
    // memory is freed when the box goes out of the scope
    let datap = data as *mut Vec<MyValue>;
    unsafe { Box::from_raw(datap) };
}

pub extern "C" fn alloc(klass: Value) -> Value {
    let data: Box<Vec<MyValue>> = Box::new(vec![]);
    let datap = Box::into_raw(data) as *mut c_void;
    unsafe { rb_data_object_wrap(klass, datap, None, Some(free)) }
}

pub extern "C" fn get_internal_data(itself: Value) -> Value {
    let rdata: *const RData = unsafe { mem::transmute(itself) };
    let datap = unsafe { (*rdata).data as *mut Vec<MyValue> };
    let the_data = unsafe { Box::from_raw(datap) };
    let result = rb_array_from_internal_values(&the_data);
    mem::forget(the_data); // don't free this memory
    result
}

fn rb_array_from_internal_values(data: &Box<Vec<MyValue>>) -> Value {
    let mut ary = unsafe { rb_ary_new() };
    for elm in data.iter() {
        let item = match elm {
            &MyValue::Vec(ref v) => rb_array_from_internal_values(v),
            &MyValue::Str(ref s) => unsafe { rb_str_new_cstr(c_str_ptr(&s.clone())) },
        };
        ary = unsafe { rb_ary_push(ary, item) }
    }
    ary
}

pub extern "C" fn set_internal_data(itself: Value, data: Value) -> Value {
    let arr = internal_values_from_rb_array(data);
    let datap = Box::into_raw(Box::new(arr)) as *mut c_void;
    let rdata: *mut RData = unsafe { mem::transmute(itself) };
    unsafe { (*rdata).data = datap };
    data
}

fn internal_values_from_rb_array(data: Value) -> Vec<MyValue> {
    let len = unsafe { rb_ary_len(data) };
    let mut arr: Vec<MyValue> = vec![];
    for i in 0..len {
        let item = unsafe { rb_ary_entry(data, i) };
        let item = match item.ty() {
            ValueType::RString => {
                let cstr = unsafe { rb_string_value_cstr(&item) };
                let string = unsafe { CStr::from_ptr(cstr) }
                    .to_string_lossy()
                    .into_owned();
                MyValue::Str(string)
            }
            ValueType::Array => MyValue::Vec(Box::new(internal_values_from_rb_array(item))),
            _ => panic!(),
        };
        arr.push(item);
    }
    arr
#[derive(Debug)]
enum MyValue {
    Vec(Box<Vec<MyValue>>),
    Str(String),
}

#[no_mangle]
pub extern "C" fn init_thing() {
    let name = c_str_ptr("Thing");
    let name = CString::new("Thing").unwrap().into_raw();
    let klass = unsafe { rb_define_class(name, rb_cObject) };
    unsafe { rb_define_alloc_func(klass, alloc) };
    wrap::define_alloc_func(klass, alloc);
    unsafe {
        rb_define_method(
            klass,
            c_str_ptr("get_internal_data"),
            get_internal_data as CallbackPtr,
            CString::new("show_me_the_data").unwrap().into_raw(),
            show_me_the_data as CallbackPtr,
            0,
        )
    };
    unsafe {
        rb_define_method(
            klass,
            c_str_ptr("set_internal_data"),
            set_internal_data as CallbackPtr,
            1,
            CString::new("mutate_that_data!").unwrap().into_raw(),
            mutate_that_data as CallbackPtr,
            0,
        )
    };
}

fn alloc(klass: Value) -> Value {
    let data: Box<Vec<MyValue>> = Box::new(vec![]);
    wrap::wrap(klass, data)
}

fn show_me_the_data(itself: Value) -> Value {
    let data: Box<Vec<MyValue>> = wrap::get(itself);
    println!("{:?}", data);
    mem::forget(data); // don't free this memory
    itself
}

fn mutate_that_data(itself: Value) -> Value {
    println!("mutating the data");
    let new_data: Box<Vec<MyValue>> = Box::new(
        vec![
            MyValue::Str("foo".to_string()),
            MyValue::Vec(Box::new(
                vec![
                    MyValue::Str("bar".to_string())
                ]
            ))
        ]
    );
    wrap::update(itself, new_data);
    itself
}

A src/wrap.rs => src/wrap.rs +48 -0
@@ 0,0 1,48 @@
use ruby_sys::{types::{c_void, CallbackPtr, RBasic, Value}};

use std::mem;

extern "C" {
    pub fn rb_define_alloc_func(klass: Value, func: CallbackPtr);
    pub fn rb_data_object_wrap(
        klass: Value,
        datap: *mut c_void,
        mark: Option<extern "C" fn(*mut c_void)>,
        free: Option<extern "C" fn(*mut c_void)>,
    ) -> Value;
}

#[repr(C)]
struct RData {
    basic: RBasic,
    dmark: Option<extern "C" fn(*mut c_void)>,
    dfree: Option<extern "C" fn(*mut c_void)>,
    pub data: *mut c_void,
}

pub fn define_alloc_func(klass: Value, alloc: fn(Value) -> Value) {
    unsafe { rb_define_alloc_func(klass, alloc as CallbackPtr) };
}

pub fn wrap<T>(klass: Value, data: Box<T>) -> Value {
    let datap = Box::into_raw(data) as *mut c_void;
    unsafe { rb_data_object_wrap(klass, datap, None, Some(free::<T>)) }
}

pub extern "C" fn free<T>(data: *mut c_void) {
    // memory is freed when the box goes out of the scope
    let datap = data as *mut T;
    unsafe { Box::from_raw(datap) };
}

pub fn get<T>(itself: Value) -> Box<T> {
    let rdata: *mut RData = unsafe { mem::transmute(itself) };
    let datap = unsafe { (*rdata).data as *mut T };
    unsafe { Box::from_raw(datap) }
}

pub fn update<T>(itself: Value, data: Box<T>) {
    let datap = Box::into_raw(data) as *mut c_void;
    let rdata: *mut RData = unsafe { mem::transmute(itself) };
    unsafe { (*rdata).data = datap };
}

M test.rb => test.rb +5 -5
@@ 4,8 4,8 @@ library = Fiddle::dlopen("target/debug/librust_rbdatatype.so")
init_thing = Fiddle::Function.new(library['init_thing'], [], Fiddle::TYPE_VOID)
init_thing.call

t = Thing.new
p t
p t.get_internal_data
t.set_internal_data(["foo", ["bar"]])
p t.get_internal_data
thing = Thing.new
p thing
thing.show_me_the_data
thing.mutate_that_data!
thing.show_me_the_data