Open danielpclark opened 6 years ago
Alright, I figured out how to use rb_scan_args
in Rust. It's pretty simple. You give it a mapping of how the parameters will be laid out as a string and then everything after that is reference pointers to Value
objects you've instantiated in Rust.
let args: [Value; 2] = [
Value::from(0),
Value::from(0)
];
unsafe {
util::rb_scan_args(
argc, // Argc
argv, // *const Value
str_to_cstring("1*").as_ptr(),
&args[0],
&args[1]
);
}
let path_self = AnyObject::from(args[0]);
let args_array = Array::from(args[1]);
rb_scan_args
replaces the values I established in the beginning of the code block. I haven't accounted for exceptions being raised in this though.
Since the argv
parameter of rb_scan_args
takes a *const Value
as ruby-sys doesn't contain the AnyObject
type I had to cordon off a custom CallbackPtr
as a ValueCallbackPtr
and re-implement a define_singleton_method
parameters to take my type. This helped me avoid the needles conversions into and out of AnyObject
steps for rb_scan_args
.
What I really would like to do is have the methods!
macro detect the method parameters and create a custom string mapping for rb_scan_args
so we can use splatted arguments anywhere. If we get exception handling covered with it as well it would be an added “type” verification for arguments 😉😉.
Excerpt from The Definitive Guide to Ruby's C API for the string mapping:
Well, if you accept a variable number of arguments you could code all of that logic yourself in the method, and make it behave like it has a fancier method definition in Ruby. Thankfully, the API has a shortcut for doing exactly that. To use it, you should use the C array function definition, then you can pass argc
and argv
along to:
int rb_scan_args(int argc, const VALUE* argv, const char* fmt, ...);
Here fmt
is a format string describing how the method arguments would look in Ruby. The string can have at most 6 characters, where each character describes a different section of the arguments. The six sections and their corresponding characters are (in order):
*
:
&
Each section is optional, so you can leave out the characters for things you don’t need. Be aware that the parsing of the format string is greedy: 1*
describes a method with one mandatory argument and a splat. If you want one optional argument and a splat you must specify 01*
. Following the format string, you must pass a VALUE*
for each Ruby argument. The number of pointers passed should equal the “total” of the six sections, though you can pass NULL
for an argument you don’t care about. For example the format string 21*&
should have 5 VALUE*
s passed (2 mandatory, 1 optional, 1 splatted, 1 block).
rb_scan_args()
unpacks argv
using the VALUE*
s you pass it and will raise a fitting exception if the wrong number of arguments were passed.
I have working splat code now in my master branch of FasterPath, although it's just for one method. You can see it here: https://github.com/danielpclark/faster_path/blob/v0.2.5/src/pathname_sys.rs#L23-L36
It would be nice to either have a dynamic size of arguments like when using splat operators. We can either have it auto-converted into an
Array
or create a new type for iterating calledSplat
orArgs
.Currently trying to use
Array
for an unknown size of parameters doesn't work.I imagine specifying in the
methods!
macro that theSplat
type will produce an iterator to sequentially produceValue
objects fromargc
andargv
.Okay I believe I found the relevant info. What we want is rb_scan_args — Ruby C Extensions Part 6: Defining Functions. Additional info on it at The Definitive Guide to Ruby's C API. Helix has it already rb_scan_args
This debug code for scan_args shows the many ways to use it: scan_args/scan_args.c and includes how to make and Array from it
return rb_ary_new_from_values(numberof(args), args)
and -ext-/test_scan_args.rb shows the Ruby test side for those C methods.