3

I am looking into example from Programming Ruby 1.9. Is it possible to create instance variables not exposed to Ruby, visible only in C - for example to initialize C-structure in t_init and use it in t_add ?

If I declare the structure or variable below the id_push, it is class structure/variable and I need instance.

Is it possible to achieve without using Data_Wrap_Struct/Data_Get_Struct or rb_iv_set/rb_iv_get, as the instance variable is not needed in Ruby, it is enough to be visible from C only?

#include "ruby.h"

static ID id_push;

// how to define instance variables here?

static VALUE t_init(VALUE self) {
    VALUE arr;
    arr = rb_ary_new(); 
    rb_iv_set(self, "@arr", arr); 
    return self;
}

static VALUE t_add(VALUE self, VALUE obj) {
    VALUE arr;
    arr = rb_iv_get(self, "@arr"); 
    rb_funcall(arr, id_push, 1, obj); 
    return arr;
}

VALUE cTest;

void Init_my_test() {
    cTest = rb_define_class("MyTest", rb_cObject); 
    rb_define_method(cTest, "initialize", t_init, 0); 
    rb_define_method(cTest, "add", t_add, 1); id_push = rb_intern("push");
}
user513951
  • 12,445
  • 7
  • 65
  • 82
bsrdjan
  • 436
  • 3
  • 10

1 Answers1

4

The C functions you are looking for are rb_ivar_set and rb_ivar_get.

rb_ivar_set takes three arguments:

  1. the VALUE-type Ruby receiver object
  2. the ID-type ivar name to assign
  3. the VALUE-type Ruby right-hand-side object.

rb_ivar_get takes two arguments:

  1. the VALUE-type Ruby receiver object
  2. the ID-type ivar name to retrieve

Ruby can store instance variables internally with many kinds of ID-type name. The only variable names made visible from the Ruby layer, though, are the ones that begin with @, e.g. rb_intern("@foo"). Leaving out the @ makes the instance variable inaccessible from the Ruby layer, but allows you to store and access it from the C layer.


Here is your code sample with this technique implemented.

#include "ruby.h"

static ID id_push;

static VALUE t_init(VALUE self) {
    VALUE arr;
    arr = rb_ary_new();
    rb_ivar_set(self, rb_intern("arr"), arr); /* <-- */
    return self;
}

static VALUE t_add(VALUE self, VALUE obj) {
    VALUE arr;
    arr = rb_ivar_get(self, rb_intern("arr")); /* <-- */
    rb_funcall(arr, id_push, 1, obj);
    return arr;
}

VALUE cTest;

void Init_my_test() {
    cTest = rb_define_class("MyTest", rb_cObject); 
    rb_define_method(cTest, "initialize", t_init, 0); 
    rb_define_method(cTest, "add", t_add, 1); id_push = rb_intern("push");
}

Test it out! It should run like this (I didn't compile the above, there may be typos):

require 'my_test'

class MyTest
  def check
    return @arr
  end
end

t = MyTest.new
t.add(1) #=> [1]
t.check #=> nil

The #check method goes looking for the @arr instance variable and comes up empty-handed. Your instance variables are safe and sound, locked up in the C layer!

user513951
  • 12,445
  • 7
  • 65
  • 82
  • 1
    That's horrendous :-) It might work though - are you certain that GC will do the right thing with these partly-accessible things? Does any well-respected extension library actually use this technique as a shortcut (to hide instance variables added by C)? – Neil Slater Mar 03 '14 at 13:45
  • 1
    OK, found difference between `rb_iv_set` and `rb_ivar_set`, it makes sense now (and +1), initially I thought you were suggesting to use `rb_iv_set` without the `@` . . . – Neil Slater Mar 03 '14 at 13:52
  • It works, I can now save/get C structures as class instance variables, visible in C only and no segfaults occur in irb when the shell tries to display them and conf.echo is active. The Ruby garbage collector will clean those variables as well, or? – bsrdjan Mar 05 '14 at 13:48
  • Yup. The garbage collector cleans them up in the exact same way it cleans up your regular `@foo` instance variables. – user513951 Mar 05 '14 at 19:13