2

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?

rmosolgo
  • 1,854
  • 1
  • 18
  • 23
  • 1
    I *think* you should be able to just pass in a hash (`rb_hash_new` for creating it, `rb_hash_aset` for setting values). Let me know if that works, than I can turn it into an answer. – Michael Kohl Oct 29 '16 at 03:01
  • Thanks for your suggestion! I updated the question with a few things I've tried ... is there another way I could try passing the hash as keywords? – rmosolgo Oct 29 '16 at 11:14
  • 2
    You’re on the right lines with your first attempt (passing a hash as the last arg), but the keys need to be symbols, not IDs. Try `ID2SYM(rb_intern("name"))` rather than just `rb_intern("name")`. I don’t know if there’s a way of converting a `char *` in C directly into a ruby symbol in one step, it looks like you need to create an ID then convert to symbol. – matt Oct 29 '16 at 14:48
  • @matt it works! Could you add that suggestion as an "Answer" so that I can accept it and give you magical stack overflow points? – rmosolgo Oct 29 '16 at 20:37
  • I think @MichaelKohl gets priority in claiming those points. – matt Oct 30 '16 at 14:13
  • Thanks @matt. I posted an answer now. – Michael Kohl Oct 31 '16 at 01:37

1 Answers1

2

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);
Michael Kohl
  • 66,324
  • 14
  • 138
  • 158