[][src]Macro rutie::wrappable_struct

macro_rules! wrappable_struct {
    (@mark_function_pointer) => { ... };
    (@mark_function_pointer , mark($object: ident) $body: block) => { ... };
    (@mark_function_definition $struct_name: ty) => { ... };
    (@mark_function_definition $struct_name: ty, mark($object: ident) $body: expr) => { ... };
    ($struct_name: ty, $wrapper: ident, $static_name: ident $($tail: tt)*) => { ... };
}

Makes a Rust struct wrappable for Ruby objects.

Note: Currently to be able to use wrappable_struct! macro, you should include lazy_static crate to the crate you are working on.

Cargo.toml

lazy_static = "0.2.1" # the version is not a strict requirement

Crate root lib.rs or main.rs

This example is not tested
#[macro_use]
extern crate lazy_static;

Arguments

  • $struct_name is name of the actual Rust struct. This structure has to be public (pub).

  • $wrapper is a name for the structure which will be created to wrap the $struct_name.

    The wrapper will be created automatically by the macro.

  • $static_name is a name for a static variable which will contain the wrapper.

    The static variable will be created automatically by the macro.

    This variable has to be passed to wrap_data() and get_data() functions (see examples).

    Also, these variables describe the structure in general, but not some specific object. So you should pass the same static variable when wrapping/getting data of the same type for different ruby objects.

    For example,

    This example is not tested
    server1.get_data(&*SERVER_WRAPPER);
    server2.get_data(&*SERVER_WRAPPER); // <-- the same `SERVER_WRAPPER`
  • (optional) mark(data) { ... } is a block which will be called during the "mark" phase of garbage collection.

    This block must be used if the struct contains any Ruby objects. The objects should be marked with GC::mark() to prevent their garbage collection.

    data argument will be yielded as a mutable reference to the wrapped struct (&mut $struct_name).

    Notes from the official MRI documentation:

    • It is not recommended to store Ruby objects in the structs. Try to avoid that if possible.

    • It is not allowed to allocate new Ruby objects in the mark function.

The result of wrappable_struct! is:

This example is not tested
wrappable_struct!(Server, ServerWrapper, SERVER_WRAPPER);

// produces

struct ServerWrapper {
    // ...
}

pub static ref SERVER_WRAPPER: ServerWrapper<Server> = // ...

Class

The class which will be used for wrapping data is Object and not Data (See Ruby issue #3072).

let data_class = Class::from_existing("Object");

Class::new("TheNewClass", Some(&data_class));

Examples

Wrap Server structs to RubyServer objects

#[macro_use] extern crate rutie;
#[macro_use] extern crate lazy_static;

use rutie::{AnyObject, Class, Fixnum, Object, RString, VM};

// The structure which we want to wrap
pub struct Server {
    host: String,
    port: u16,
}

impl Server {
    fn new(host: String, port: u16) -> Self {
        Server {
            host: host,
            port: port,
        }
    }

    fn host(&self) -> &str {
        &self.host
    }

    fn port(&self) -> u16 {
        self.port
    }
}

wrappable_struct!(Server, ServerWrapper, SERVER_WRAPPER);

class!(RubyServer);

methods!(
    RubyServer,
    itself,

    fn ruby_server_new(host: RString, port: Fixnum) -> AnyObject {
        let server = Server::new(host.unwrap().to_string(),
                                 port.unwrap().to_i64() as u16);

        Class::from_existing("RubyServer").wrap_data(server, &*SERVER_WRAPPER)
    }

    fn ruby_server_host() -> RString {
        let host = itself.get_data(&*SERVER_WRAPPER).host();

        RString::new_utf8(host)
    }

    fn ruby_server_port() -> Fixnum {
        let port = itself.get_data(&*SERVER_WRAPPER).port();

        Fixnum::new(port as i64)
    }
);

fn main() {
    let data_class = Class::from_existing("Object");

    Class::new("RubyServer", Some(&data_class)).define(|itself| {
        itself.def_self("new", ruby_server_new);

        itself.def("host", ruby_server_host);
        itself.def("port", ruby_server_port);
    });
}

To use the RubyServer class in Ruby:

server = RubyServer.new("127.0.0.1", 3000)

server.host == "127.0.0.1"
server.port == 3000

RustyArray

Custom array implementation using a vector which contains AnyObjects.

#[macro_use] extern crate rutie;
#[macro_use] extern crate lazy_static;

use std::ops::{Deref, DerefMut};

use rutie::{AnyObject, Class, Fixnum, GC, NilClass, Object, VM};

pub struct VectorOfObjects {
    inner: Vec<AnyObject>,
}

impl VectorOfObjects {
    fn new() -> Self {
        VectorOfObjects {
            inner: Vec::new(),
        }
    }
}

impl Deref for VectorOfObjects {
    type Target = Vec<AnyObject>;

    fn deref(&self) -> &Vec<AnyObject> {
        &self.inner
    }
}

impl DerefMut for VectorOfObjects {
    fn deref_mut(&mut self) -> &mut Vec<AnyObject> {
        &mut self.inner
    }
}

wrappable_struct! {
    VectorOfObjects,
    VectorOfObjectsWrapper,
    VECTOR_OF_OBJECTS_WRAPPER,

    // Mark each `AnyObject` element of the `inner` vector to prevent garbage collection.
    // `data` is a mutable reference to the wrapped data (`&mut VectorOfObjects`).
    mark(data) {
        for object in &data.inner {
            GC::mark(object);
        }
    }
}

class!(RustyArray);

methods! {
    RustyArray,
    itself,

    fn new() -> AnyObject {
        let vec = VectorOfObjects::new();

        Class::from_existing("RustyArray").wrap_data(vec, &*VECTOR_OF_OBJECTS_WRAPPER)
    }

    fn push(object: AnyObject) -> NilClass {
        itself.get_data_mut(&*VECTOR_OF_OBJECTS_WRAPPER).push(object.unwrap());

        NilClass::new()
    }

    fn length() -> Fixnum {
        let length = itself.get_data(&*VECTOR_OF_OBJECTS_WRAPPER).len() as i64;

        Fixnum::new(length)
    }
}

fn main() {
    let data_class = Class::from_existing("Object");

    Class::new("RustyArray", Some(&data_class)).define(|itself| {
        itself.def_self("new", new);

        itself.def("push", push);
        itself.def("length", length);
    });
}

To use the RustyArray class in Ruby:

array = RustyArray.new

array.push(1)
array.push("string")
array.push(:symbol)

array.length == 3