1

I have an array (can be more than just one array) in C. I need to build an interface so that ruby can modify/read the array values. I am building ruby modules in C so they later are used in ruby.

C file:

#include <ruby.h>
VALUE ParentModule;
uint8 variable = 7;
uint8 array[2];

VALUE get_variable(VALUE self)
{
  return INT2NUM(variable);
}
VALUE set_variable(VALUE self, VALUE x)
{
  variable = NUM2UINT(x);
  return Qnil;
}

void Init_extension(void)
{
  ParentModule = rb_define_module("ParentModule");
  rb_define_method(ParentModule, "variable", get_variable, 0);
  rb_define_method(ParentModule, "variable=", set_variable, 1);
}

Ruby file:

class Thing
  def initialize
    extend ParentModule
  end
end
c = Thing.new
c.variable #=> will return the value 7
c.variable= 10 #=> will write 10 to variable in the C section.
c.variable #=> returns 10

So all this works great, but now I need to be able to do the same with an array. What I tried:

C file:

VALUE get_array_0(VALUE self)
{
  return INT2NUM(array[0]);
}
VALUE set_array_0(VALUE self, VALUE x)
{
  array[0] = NUM2UINT(x);
  return Qnil;
}

/* this line is inside Init_extension function */
rb_define_method(ParentModule, "array[0]", get_array_0, 0);

What I am trying to do is name the set/get method to give an impression in ruby that I am "using" an array when is just really an interface to interact with the array that exists in C. The C file compiles fine but when I try to call the method from Ruby, it complains saying that "array" is not a method

Ruby:

c = Thing.new
c.array[0] #=> NoMethodError (undefined method `array' for #<Thing:0x00000234523>)

What would be the best way to achieve this? (It must work for 2D arrays as well)

NOTE: Please edit my question if you find any information redundant.

3
  • Can you try c.send("array[0]")? I think Ruby is trying to all c.array and you have the C function as array[0]. Might want to make a different name in C? rb_define_method(ParentModule, "array0", get_array_0, 0); Commented Feb 8, 2019 at 14:43
  • Throws the same error with c.send. I don't think the problem is the name, I think Ruby sees the [] after array and might be trying to treat it as an array/hash element. But that is just my guess. Commented Feb 8, 2019 at 14:48
  • Yes, I agree. I would remove the brackets in the C definition. The send suggestion was just a hail mary. array[0] isn't a valid method name anyways. Commented Feb 8, 2019 at 15:06

2 Answers 2

1

What you want is not exactly possible. There is only one way that Ruby interprets this statement:

c.array[0]

That's equivalent to

c.array().[](0)

In other words, two method calls: array with no arguments called on c and then [] with one argument called on the return value of array. If that's the syntax you want, then you'll need to define your classes in a way such that these methods exist: ParentModule will need an array method that returns something responding to []. Since you don't want this to be an actual Array, you'll need to define another object with the [] method (this object can call back to ParentModule to do whatever you want).

Sign up to request clarification or add additional context in comments.

2 Comments

So this means that is not possible to provide the parameter inside the [] function to be created right? It will always have to be [] and then the parameter (param)?
@mareiou no, you can call it as array[0] or array.[](0). They do the same thing. What is not possible is making Ruby identify array[0] as a single method call. It is always going to be two method calls. You can define it so it works sort of like a single method call, but that's on you to hack around it.
0

You can use [] and []= as the method names to make your object appear array-like in Ruby:

rb_define_method(ParentModule, "[]", get_array, 1);
rb_define_method(ParentModule, "[]=", set_array, 2);

[] takes a single argument, the index of the item you want to look at, and []= takes two arguments, the index and the new value.

You can then implement them something like this:

VALUE get_array(VALUE self, VALUE idx)
{
  return INT2NUM(array[NUM2INT(idx)]);
}

VALUE set_array(VALUE self, VALUE idx, VALUE x)
{
  array[NUM2INT(idx)] = NUM2UINT(x);
  return Qnil;
}

(Obviously this is just a simple example to show the idea, in reality you would want to check the index so it isn’t out of bounds, and also check the values so they make sense for the array type).

These methods will then be directly available on your object:

c = Thing.new
c[0] = 3
p c[0]

2 Comments

What if I want to access two arrays instead of only 1? I need to be able to create different method names in the module in order to achieve this.
@mareiou if you want to have multiple separate arrays in the object, then they will each effectively be separate objects so you will likely need to create some helper objects that will have the [] methods defined on them. The same will be true if you want to make calls like foo[a][b], the first [] call will need to return an object that you can then call [] on again. You might be able to get something like foo[a,b] to work, [] can take more than one argument.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.