Sunday, January 16, 2011

Writing Ruby Extensions in C - Part 10, Hashes

This is the tenth in my series of posts about writing ruby extensions in C. The first post talked about the basic structure of a project, including how to set up building. The second post talked about generating documentation. The third post talked about initializing the module and setting up classes. The fourth post talked about types and return values. The fifth post focused on creating and handling exceptions. The sixth post talked about ruby catch and throw blocks. The seventh post talked about dealing with numbers. The eighth post talked about strings. The ninth post focused on arrays. This post will look at hashes.


The nice thing about hashes in ruby C extensions is that they act very much like the ruby hashes they represent. There are a few functions to know about:
  • rb_hash_new() - create a new ruby Hash
  • rb_hash_aset(hash, key, value) - set the hash key to value
  • rb_hash_aref(hash, key) - get the value for hash key
  • rb_hash_foreach(hash, callback, args) - call callback for each key,value pair in the hash. Callback must have a prototype of int (*cb)(VALUE key, VALUE val, VALUE in)

An example will demonstrate this:

 1) int do_print(VALUE key, VALUE val, VALUE in) {
 2)      fprintf(stderr, "Input data is %s\n", StringValueCStr(in));
 4)      fprintf(stderr, "Key %s=>Value %s\n", StringValueCStr(key),
 5)              StringValueCStr(val));
 7)      return ST_CONTINUE;
 8) }
10) VALUE result;
11) VALUE val;
13) result = rb_hash_new();
14) // result is now {}
15) rb_hash_aset(result, rb_str_new2("mykey"),
16)              rb_str_new2("myvalue"));
17) // result is now {"mykey"=>"myvalue"}
18) rb_hash_aset(result, rb_str_new2("anotherkey"),
19)              rb_str_new2("anotherval"));
20) // result is now {"mykey"=>"myvalue",
21) //                "anotherkey"=>"anotherval"}
22) rb_hash_aset(result, rb_str_new2("mykey"),
23)              rb_str_new2("differentval"));
24) // result is now {"mykey"=>"differentval",
25) //                "anotherkey"=>"anotherval"}
26) val = rb_hash_aref(result, rb_str_new2("mykey"));
27) // result is now {"mykey"=>"differentval",
28) //                "anotherkey"=>"anotherval"},
29) // val is "differentval"
30) rb_hash_delete(result, rb_str_new2("mykey"));
31) // result is now {"anotherkey"=>"anotherval"}
33) rb_hash_foreach(result, do_print, rb_str_new2("passthrough"));

Most of this is pretty straightforward. The most interesting part of this is line 33, where we perform an operation on all elements in the hash by utilizing a callback. This callback is defined on lines 1 through 8, and takes in the key, value, and the user data provided to the original rb_hash_foreach() call. The return code from the callback defines what happens to the processing of the rest of the hash. If the return value is ST_CONTINUE, then the rest of the hash is processed as normal. If the return value is ST_STOP, then no further processing of the hash is done. If the return value is ST_DELETE, then the current hash key is deleted from the hash and the rest of the hash is processed. If the return value is ST_CHECK, then the hash is checked to see if it has been modified during this operation. If so, processing of the hash stops.

Update: Fixed up the example code to show on the screen.

1 comment:

  1. Hi I am using you example and getting this error
    candidate function not viable: cannot convert argument of incomplete type 'void' to 'VALUE' (aka 'unsigned long')
    VALUE rb_hash_aref(VALUE, VALUE);