Macro ruru::wrappable_struct [] [src]

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

#[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 structcure 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 wrapper static variable when wrapping/getting data of the same kind for different ruby objects.

    For example,

    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:

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 must inherit from Data class instead of Object.

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

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

Examples

Wrap Server structs to RubyServer objects

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

use ruru::{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(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("Data");

    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 ruru;
#[macro_use] extern crate lazy_static;

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

use ruru::{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(&*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("Data");

    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