Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to convert options hash with Rice? #202

Open
lbenporat-cloudinary opened this issue Jan 18, 2024 · 5 comments
Open

How to convert options hash with Rice? #202

lbenporat-cloudinary opened this issue Jan 18, 2024 · 5 comments

Comments

@lbenporat-cloudinary
Copy link

lbenporat-cloudinary commented Jan 18, 2024

I'm integrating Rice into Ruby-C extension and wondered what is the Rice way to convert options hash to cpp types. I ended up checking for each type explicitly, is there a generic way to do it?
For example, Ruby code I'd like to execute:

opts = { "opt_1" => 1, "opt_2" => "string", "opt_3" => 5.0 }
MyClass.new.foo(opts)

Underlying cpp class:

class MyCppClass {
  ...
  void foo(int opt_1, std::string opt_2, float opt_3) {
    // Do something with args
  }
}
@jasonroelofs
Copy link
Collaborator

That should work with one minor adjustment, you need to explicitly splat out the hash to be considered "keyword arguments" with the double star **:

opts = { "opt_1" => 1, "opt_2" => "string", "opt_3" => 5.0 }
MyClass.new.foo(**opts)

Then, as long as you wrap foo and explicitly define each Arg with a name and correct type it should map those, or error if the types don't match.

@jasonroelofs
Copy link
Collaborator

Ok, apologies, I went and made absolutely sure, and no Rice does not currently support this kind of splatting. It still comes across as a Hash argument.

So for now you'll need to do what it sounds like you've already done, splitting the Hash apart yourself and passing the values through as needed.

@cfis
Copy link
Collaborator

cfis commented Feb 14, 2024

It would be a nice addition to handle keyword args. When calling define_function/method Rice currently supports Arg and Return. Maybe add KwArg?

.define_method("foo",
   &MyClass::foo,
   KwArg("opt_1"), KwArg("opt_2"), KwArg("opt_3"), KwArg("opt_4") = "default value");

Rice could then update its code that generates an rb_scan_args format (see https://ruby-doc.org/3.3.0/extension_rdoc.html) to accept keyword arguments.

Which in this case would be 00:. After calling rb_scan_args Rice would then need to call rb_get_kwargs to extract the arguments out of the keyword hash (they all get packaged together when sent to C) and convert them into C++ values.

Having said all that, Rice determines the number of of arguments to a function at compile time based on the C++ function definition and send a tuple of that size to rb_scan_args. Adding keywords breaks that (there will be less items since keywords get compressed in a Hash. So doing this would involve some ugly code - but it would be doable :(

Nice summary here - https://blog.appsignal.com/2023/01/18/how-to-parse-arguments-in-your-ruby-c-extension.html.

Pybind does this a bit differently - see https://pybind11.readthedocs.io/en/stable/advanced/functions.html#accepting-args-and-kwargs.

@jasonroelofs
Copy link
Collaborator

The intent of making Arg take a name was to hopefully adapt it to support keyword arguments automatically. However it does look like this would be quite difficult given the myriad of ways you can call a Ruby method. Another example, Magnus (basically Rice but for Rust) has an explicit kwargs! macro for marking which parameters should be used as keyword arguments.

We'll probably need to play with some ideas here to see what would work and what doesn't.

@cfis
Copy link
Collaborator

cfis commented Feb 25, 2024

Thanks for the link to Magnus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants