~tim/rust-ruby-wrap-data

7ecced3a3b68584cdcd68f5e9e79b4950368e92a — Tim Morgan 2 years ago b961e7a
Add more docs
3 files changed, 132 insertions(+), 8 deletions(-)

A LICENSE
M README.md
M src/lib.rs
A LICENSE => LICENSE +21 -0
@@ 0,0 1,21 @@
MIT License

Copyright (c) 2018 Tim Morgan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

M README.md => README.md +62 -0
@@ 6,6 6,62 @@ what Ruby's `Data_Wrap_Struct` macro does. That is to say, you can store a Rust

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: Option<Box<MyValue>> = ruby_wrap_data::remove(thing);
    assert!(data.is_some());

    // if you try to remove it again, you get None
    let data: Option<Box<MyValue>> = ruby_wrap_data::remove(thing);
    assert!(data.is_none());

    // set new data on the object
    let new_data = Box::new(MyValue { val : 2 });
    ruby_wrap_data::set(thing, new_data);
}
```

## Testing

Assuming you're using rbenv (if not, sorry, you're on your own):


@@ 21,3 77,9 @@ You may need to help Rust find the libruby.so file, like this:
export LD_LIBRARY_PATH=$HOME/.rbenv/versions/2.5.1/lib
RUBY=$(rbenv which ruby) cargo test
```

## Copyright and License

Copyright Tim Morgan

Licensed MIT

M src/lib.rs => src/lib.rs +49 -8
@@ 8,7 8,7 @@
//!
//! ## Example
//!
//! ```
//! ```rust
//! extern crate ruby_sys;
//! extern crate ruby_wrap_data;
//!


@@ 49,7 49,12 @@
//!     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::remove(thing).unwrap();
//!     let data: Option<Box<MyValue>> = ruby_wrap_data::remove(thing);
//!     assert!(data.is_some());
//!
//!     // if you try to remove it again, you get None
//!     let data: Option<Box<MyValue>> = ruby_wrap_data::remove(thing);
//!     assert!(data.is_none());
//!
//!     // set new data on the object
//!     let new_data = Box::new(MyValue { val : 2 });


@@ 91,17 96,41 @@ pub fn define_alloc_func(klass: Value, alloc: fn(Value) -> Value) {
    unsafe { rb_define_alloc_func(klass, alloc as CallbackPtr) };
}

/// Creates a new instance of the given class, wrapping the given
/// heap-allocated data type.
///
/// # Arguments
///
/// * `klass` - a Ruby Class
/// * `data`  - a Box<T> - the data you wish to embed in the Ruby object
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) };
}

/// Removes and returns the wrapped data from the given Ruby object.
/// Returns None if the data is currently NULL.
///
/// # Arguments
///
/// * `object` - a Ruby object
///
/// # Notes
///
/// You will need to specify the data type of the variable since it
/// cannot be inferred:
///
/// ```compile_fail
/// let data: Option<Box<MyValue>> = ruby_wrap_data::remove(thing);
/// ```
///
/// Also note, if you wish to peek at the data without removing it,
/// you will need to put it back using `set`, like this:
///
/// ```compile_fail
/// let data: Option<Box<MyValue>> = ruby_wrap_data::remove(thing);
/// ruby_wrap_data::set(thing, data.unwrap());
/// ```
pub fn remove<T>(object: Value) -> Option<Box<T>> {
    let rdata = rdata(object);
    let datap = unsafe { (*rdata).data as *mut T };


@@ 113,12 142,24 @@ pub fn remove<T>(object: Value) -> Option<Box<T>> {
    }
}

/// Sets the wrapped data on the given Ruby object.
///
/// # Arguments
///
/// * `object` - a Ruby object
/// * `data`   - a Box<T> - the data you wish to embed in the Ruby object
pub fn set<T>(object: Value, data: Box<T>) {
    let rdata = rdata(object);
    let datap = Box::into_raw(data) as *mut c_void;
    unsafe { (*rdata).data = datap };
}

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) };
}

fn set_none(object: Value) {
    let rdata = rdata(object);
    unsafe { (*rdata).data = ptr::null_mut() };