f5e88b97777117d08cd8e5d1f71649d703b3531b — Tim Morgan 1 year, 5 months ago 9f7cbbc
Start turning this into a proper crate
6 files changed, 182 insertions(+), 132 deletions(-)

M .watchr
M Cargo.toml
M README.md
M src/lib.rs
D src/wrap.rs
D test.rb
M .watchr => .watchr +2 -11
@@ 4,18 4,9 @@
   puts
   puts '========================================='
   puts
-  _, stdout, wait_thr = Open3.popen2('cargo build')
+  _, stdout, wait_thr = Open3.popen2('cargo test')
   print stdout.getc until stdout.eof?
   wait_thr.value.success?
 end
 
-def test
-  puts
-  puts '========================================='
-  puts
-  _, stdout, wait_thr = Open3.popen2('ruby test.rb')
-  print stdout.getc until stdout.eof?
-  wait_thr.value.success?
-end
-
-watch('^src/.*|^test\.rb') { compile && test }
+watch('^src/.*|^test\.rb') { compile }

M Cargo.toml => Cargo.toml +7 -5
@@ 1,11 1,13 @@
 [package]
-name = "rust-rbdatatype"
+name = "ruby-wrap-data"
 version = "0.1.0"
 authors = ["Tim Morgan <tim@timmorgan.org>"]
+description = "Build a Ruby object that wraps Box<T>"
+homepage = "https://github.com/seven1m/rust-ruby-wrap-data"
+documentation = "https://docs.rs/ruby-wrap-data"
+repository = "https://github.com/seven1m/rust-ruby-wrap-data"
+keywords = ["ruby"]
+license = "MIT"
 
 [dependencies]
-libc = "0.2.37"
 ruby-sys = "0.3.0"
-
-[lib]
-crate-type = ["dylib"]

M README.md => README.md +19 -5
@@ 1,9 1,23 @@
-# Rust "C" extension that stores data inside a Ruby object
+# Ruby Wrap Data
 
-This is a bit of Rust code that demonstrates how to store any data you want inside a Ruby object
-using `rb_data_object_wrap`.
+`ruby_wrap_data` is a Rust crate that provides a fairly low-level means of doing
+what Ruby's `Data_Wrap_Struct` macro does. That is to say, you can store a Rust
+`Box<T>` inside a Ruby object and get it back out again.
+
+Any heap-allocated struct, enum, or whatever should work.
+
+## Testing
+
+Assuming you're using rbenv (if not, sorry, you're on your own):
+
+```
+CONFIGURE_OPTS=--enable-shared rbenv install
+RUBY=$(rbenv which ruby) cargo test
+```
+
+You may need to help Rust find the libruby.so file, like this:
 
 ```
-RUBY=$(rbenv which ruby) cargo build
-ruby test.rb
+export LD_LIBRARY_PATH=$HOME/.rbenv/versions/2.5.1/lib
+RUBY=$(rbenv which ruby) cargo test
 ```

M src/lib.rs => src/lib.rs +154 -48
@@ 1,60 1,166 @@
-extern crate libc;
+//! # Ruby Wrap Data
+//!
+//! `ruby_wrap_data` is a crate that provides a fairly low-level means of doing
+//! what Ruby's `Data_Wrap_Struct` macro does. That is to say, you can store a
+//! Rust `Box<T>` inside a Ruby object and get it back out again.
+//!
+//! Any heap-allocated struct, enum, or whatever should work.
+//!
+//! ## Example
+//!
+//! ```
+//! extern crate ruby_sys;
+//! extern crate ruby_wrap_data;
+//!
+//! use ruby_sys::{
+//!     class::{rb_class_new_instance, rb_define_class},
+//!     rb_cObject,
+//!     types::Value,
+//!     value::RubySpecialConsts::{Nil},
+//!     vm::ruby_init
+//! };
+//!
+//! use std::ffi::CString;
+//! use std::mem;
+//!
+//! const RB_NIL: Value = Value { value: Nil as usize };
+//!
+//! struct MyValue {
+//!     pub val: u16
+//! }
+//!
+//! fn alloc(klass: Value) -> Value {
+//!     // build your data and put it on the heap
+//!     let data = Box::new(MyValue { val: 1 });
+//!     // call `wrap()`, passing your klass and data
+//!     ruby_wrap_data::wrap(klass, data)
+//! }
+//!
+//! fn main() {
+//!     // you may need to start the ruby vm
+//!     unsafe { ruby_init() };
+//!
+//!     // create a ruby class and attach your alloc function
+//!     let name = CString::new("Thing").unwrap().into_raw();
+//!     let klass = unsafe { rb_define_class(name, rb_cObject) };
+//!     ruby_wrap_data::define_alloc_func(klass, alloc);
+//!
+//!     // create a new instance of the class
+//!     let thing = unsafe { rb_class_new_instance(0, &RB_NIL, klass) };
+//!
+//!     // get the data out of the ruby object
+//!     let data: Box<MyValue> = ruby_wrap_data::get(thing);
+//!     // forget the data so it's not freed
+//!     mem::forget(data);
+//!
+//!     // set new data on the object
+//!     let new_data = Box::new(MyValue { val : 2 });
+//!     ruby_wrap_data::set(thing, new_data);
+//! }
+//! ```
 extern crate ruby_sys;
 
-mod wrap;
+use ruby_sys::types::{c_void, CallbackPtr, RBasic, Value};
 
-use ruby_sys::{class::{rb_define_class, rb_define_method}, rb_cObject, types::{CallbackPtr, Value}};
-
-use std::ffi::CString;
 use std::mem;
 
-#[derive(Debug)]
-enum MyValue {
-    Vec(Vec<MyValue>),
-    Str(String),
-}
-
-#[no_mangle]
-pub extern "C" fn init_thing() {
-    let name = CString::new("Thing").unwrap().into_raw();
-    let klass = unsafe { rb_define_class(name, rb_cObject) };
-    wrap::define_alloc_func(klass, alloc);
-    unsafe {
-        rb_define_method(
-            klass,
-            CString::new("show_me_the_data").unwrap().into_raw(),
-            show_me_the_data as CallbackPtr,
-            0,
-        )
-    };
-    unsafe {
-        rb_define_method(
-            klass,
-            CString::new("mutate_that_data!").unwrap().into_raw(),
-            mutate_that_data as CallbackPtr,
-            0,
-        )
-    };
+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,
+}
+
+/// Defines an 'alloc' function for a Ruby class. Such a function should
+/// build your initial data and call `wrap(klass, data)`.
+///
+/// # Arguments
+///
+/// * `klass` - a Ruby Class
+/// * `alloc` - a function taking a Ruby Value and returning a Ruby Value
+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>)) }
+}
+
+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 = rdata(itself);
+    let datap = unsafe { (*rdata).data as *mut T };
+    unsafe { Box::from_raw(datap) }
 }
 
-fn alloc(klass: Value) -> Value {
-    let data: Box<Vec<MyValue>> = Box::new(vec![]);
-    wrap::wrap(klass, data)
+pub fn set<T>(itself: Value, data: Box<T>) {
+    let rdata = rdata(itself);
+    let datap = Box::into_raw(data) as *mut c_void;
+    unsafe { (*rdata).data = datap };
 }
 
-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 rdata(object: Value) -> *mut RData {
+    unsafe { mem::transmute(object) }
 }
 
-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(vec![MyValue::Str("bar".to_string())]),
-    ]);
-    wrap::set(itself, new_data);
-    itself
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use ruby_sys::{
+        class::{rb_class_new_instance, rb_define_class},
+        rb_cObject,
+        types::Value,
+        value::RubySpecialConsts::{Nil},
+        vm::ruby_init
+    };
+
+    use std::ffi::CString;
+    use std::mem;
+
+    const RB_NIL: Value = Value { value: Nil as usize };
+
+    #[derive(Debug,PartialEq)]
+    struct MyValue {
+        pub val: u16
+    }
+
+    fn alloc(klass: Value) -> Value {
+        let data = Box::new(MyValue { val: 1 });
+        wrap(klass, data)
+    }
+
+    #[test]
+    fn it_works() {
+        unsafe { ruby_init() };
+        let name = CString::new("Thing").unwrap().into_raw();
+        let klass = unsafe { rb_define_class(name, rb_cObject) };
+        define_alloc_func(klass, alloc);
+        let thing = unsafe { rb_class_new_instance(0, &RB_NIL, klass) };
+        let data: Box<MyValue> = get(thing);
+        assert_eq!(*data, MyValue { val: 1 });
+        mem::forget(data);
+        let new_data = Box::new(MyValue { val : 2 });
+        set(thing, new_data);
+        let data: Box<MyValue> = get(thing);
+        assert_eq!(*data, MyValue { val: 2 });
+        mem::forget(data);
+    }
 }

D src/wrap.rs => src/wrap.rs +0 -52
@@ 1,52 0,0 @@
-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>)) }
-}
-
-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 = rdata(itself);
-    let datap = unsafe { (*rdata).data as *mut T };
-    unsafe { Box::from_raw(datap) }
-}
-
-pub fn set<T>(itself: Value, data: Box<T>) {
-    let rdata = rdata(itself);
-    let datap = Box::into_raw(data) as *mut c_void;
-    unsafe { (*rdata).data = datap };
-}
-
-fn rdata(object: Value) -> *mut RData {
-    unsafe { mem::transmute(object) }
-}

D test.rb => test.rb +0 -11
@@ 1,11 0,0 @@
-require 'fiddle'
-
-library = Fiddle::dlopen("target/debug/librust_rbdatatype.so")
-init_thing = Fiddle::Function.new(library['init_thing'], [], Fiddle::TYPE_VOID)
-init_thing.call
-
-thing = Thing.new
-p thing
-thing.show_me_the_data
-thing.mutate_that_data!
-thing.show_me_the_data