Search code examples
rubyruby-c-extensionkeyword-argument

How to pass keywords to a Ruby method from a C extension?


I'm working on a C extension for Ruby and I want to call a method which has required keyword arguments, like this:

class Word 
  def initialize(line:, col:, value:)
  end 
end 

In C, I'm familiar, with calling Ruby methods via rb_funcall and rb_funcallv, but I can't figure out how to pass keyword arguments!

Here are a few things I've tried:

Pass a hash as the last positional argument with rb_funcall:

VALUE kwargs = rb_hash_new();
rb_hash_aset(kwargs, rb_intern("name"), rb_str_new2(name));
// ... 
rb_funcall(Word_Class, rb_intern("new"), 1, kwargs);
// result: ArgumentError: wrong number of arguments (given 1, expected 0)

Pass it as the last member of the argv array with rb_funcallv:

// initialize `kwargs` as above
VALUE argv = rb_ary_new();
rb_ary_push(argv, kwargs);
rb_funcallv(Word_Class, rb_intern("new"), 1, &argv);
// result: ArgumentError: wrong number of arguments (given 1, expected 0)

Pass 0 as argc, even though argv is length 1:

// initialize `argv` as above
rb_funcallv(Word_Class, rb_intern("new"), 0, &argv);
// ArgumentError: missing keywords: line, col, value

Is it possible? How is it done? Is there anything else I can try?


Solution

  • You can pass in a hash. Note that to create symbol keys you need a call of the form ID2SYM(rb_intern(char*)) since rb_intern returns an ID, which ID2SYM turns into an actual Ruby symbol.

    VALUE kwargs = rb_hash_new();
    rb_hash_aset(kwargs, ID2SYM(rb_intern("name")), rb_str_new2(name));
    // ... 
    rb_funcall(Word_Class, ID2SYM(rb_intern("new")), 1, kwargs);