If you’re writing a C extension for Ruby, and you’ve got method that has optional arguments, be sure to use rb_scan_args(). Do not count argc. Read on if you want to know why.
First, let’s start with an example of bad code:
/* Example 1 */
static VALUE my_func(int argc, VALUE* argv, VALUE self)
{
VALUE v_name, v_age;
switch(argc){
case 1:
v_name = argv[0];
v_age = INT2FIX(25);
printf("No age; defaulting to 25\n");
break;
case 2:
v_name = argv[0];
v_age = argv[1];
printf("Age set\n");
break;
default:
rb_raise (rb_eRuntimeError, "invalid arguments");
}
/* Do something with v_name and v_age */
return Qnil;
}
Based on this example, it looks like the programmer wanted the my_func method to take one mandatory argument (the name) and one optional argument (the age). If no age is provided by the end user, default to 25.
Before I go on to explain why Example 1 has problems, let me show you a good example so that I can compare them point by point later on. Assume this is a method of a ‘Foo’ class:
/* Example 2 */
static VALUE my_func(int argc, VALUE* argv, VALUE self)
{
VALUE v_name, v_age;
rb_scan_args(argc, argv, "11", &v_name, &v_age);
if(NIL_P(v_age)){
printf("No age; defaulting to 25\n");
v_age = INT2FIX(25);
}
else{
printf("Age set\n");
}
/* Do something with v_name and v_age */
return Qnil;
}
Let’s start with the obvious. Example 2 is not only a few lines shorter, it’s easier to read. That’s always nice.
It’s also easier to maintain. If we were to refactor this method to accept an optional third argument, Example 1 would require yet another case + break statement. Worse, if we need to handle “if arg1 and arg3 but not arg2″ we’ll have to drop the switch statement completely and resort to a bunch of if/else clauses. Ick.
In example two, we would merely have to alter the rb_scan_args() line slightly to handle a potential 3rd argument. Logic changes would be easier to handle, since we would be checking their values individually instead of trying to base our logic on an argc count.
The second issue with Example 1 is that it requires manually raising an exception if the argument count is bad. If you look at the default action for the switch statement it has an explicit rb_raise() call. The rb_scan_args() function, on the other hand, will automatically raise an ArgumentError if you try to pass too many (or too few) arguments. No extra code required.
There is more to this issue than automatic arity checking, however. The manual handling in Example 1 doesn’t provide any detail in the error message itself. If you use rb_scan_args(), it will not only raise an error, it will also tell you how many arguments you tried to pass versus how many arguments the method actually accepts:
Foo.new.my_func("Dan", 25, 9999)
=> ArgumentError: wrong number of arguments (3 for 2)
Our code from Example 1 would merely have returned “invalid arguments” without giving us a clue as to what we did wrong.
One last note about the exception handling in Example 1 - it returns the wrong kind of error! It raises a RuntimeError instead of an ArgumentError, which is what Ruby programmers would expect. By using rb_scan_args() you can avoid making yet another mistake.[1]
Lastly, and most importanly, there’s a potentially serious bug. What happens if the user passes an explicit nil? Consider what would happens with the following output:
Foo.new.my_func(”Dan”) # No age, defaulting to 25
Foo.new.my_func(”Dan”, 37) # Age set
Foo.new.my_func(”Dan”, nil) # Age set ?!?!
Did we really want to handle explicit nils this way? Probably not. Your end users may not always know in advance what they’re passing as the second argument to my_func. While not much of an issue in our example, it could be a much bigger issue in more complex code where you could have multiple dispatch going on internally.
The code in Example 2 does the right thing - it explicitly checks for a non-nil age instead of assuming that an argc count of 2 means there are two legitimate arguments.
Explicitly checking each argument greatly reduces the possibility of your code doing the wrong thing when dealing with explicit nil (or false) [2]. Indeed, it was this sort of code that led me to always add tests for explicit nil and false as a regular part of my test suite regimen.
One last point. For some time it was believed that counting argc was faster than using rb_scan_args(). I put that myth to rest in this thread, where my benchmarks demonstrate that the speed difference is so insignificant as to be irrelevant.
To sum up, always use rb_scan_args() instead of counting argc because:
- It’s shorter and cleaner
- It’s easier to maintain and refactor
- It does automatic arity checking for you
- It’s less error prone
- It’s plenty fast
Enjoy!
[1] I didn’t just make this up - I’ve seen it.
[2] And it can often be eliminated completely by using the defacto type checking that are builtin to methods like StringValuePtr() or INT2NUM() in case they try to pass a bogus non-nil value.

I really am glad to see posts related to C extensions. This info always seems a bit hard to find.
Daniel, do you have good general references on how to do extensions with Ruby? A post on those would be grand.
I'm not disagreeing, but what about using a va_list for variable numbers of arguments?
Matthew - If you use va_args you're still in the same boat of manually counting arguments and raising exceptions by hand instead of letting rb_scan_args() do the work for you. That approach is probably even more error prone than what I showed in this article. So, please don't do that. :)
Great article. We need more of these :-)
Greg, one of the better ones that I've seen was from Mark Volkmann at http://www.ociweb.com/mark/NFJS/RubyCExtensions.pdf. It has one or two mistakes, but it's pretty good.
Great, thanks!
I was experimenting with C extensions and had a weird problem that i was not able to solve and i hope you can help me if possible.
I've built an extension and it is working fine in both a console and GTK applications. When i tried to use the extension in Rails, i had some issues. I am able to create an instance of the class but i am not able to send any messages to the defined methods in the extension! When i list the methods, i find only those inherited from the Object type.
any ideas?