Search code examples
rakumoarvm

How to return a Hash/Raku object from native call?


I am writing a library that uses NativeCall, it would be very convenient for me to be able to return a Raku Hash from an exported function. How can I do this?

For example, in Ruby, if I wanted to return a Hash from C, I would do something like:

#include "ruby.h"

VALUE make_hash() {
    VALUE hash = rb_hash_new();
    return hash;
}

I am interested to see if this can be done, I was thinking that maybe I would need to use a MoarVM header or something. But I'm not sure.

What I'm trying to do is write a C function that takes in a String does some stuff, then returns a Raku hash.


Solution

  • I have done roughly this for Rust over here (this is a collection of some Raku-Rust Nativecall code examples, not a module)...

    First the raku:

    ## Rust FFI Omnibus: Objects
    ## http:##jakegoulding.com/rust-ffi-omnibus/objects/
    
    class ZipCodeDatabase is repr('CPointer') {
        sub zip_code_database_new() returns ZipCodeDatabase is native($n-path) { * }
        sub zip_code_database_free(ZipCodeDatabase)         is native($n-path) { * }
        sub zip_code_database_populate(ZipCodeDatabase)     is native($n-path) { * }
        sub zip_code_database_population_of(ZipCodeDatabase, Str is encoded('utf8'))
                                             returns uint32 is native($n-path) { * }
    
        method new {
            zip_code_database_new
        }
    
        submethod DESTROY {        # Free data when the object is garbage collected.
            zip_code_database_free(self);
        }
    
        method populate {
            zip_code_database_populate(self)
        }
    
        method population_of( Str \zip ) {
            zip_code_database_population_of(self, zip);
        }
    }
    
    my \database = ZipCodeDatabase.new;
    database.populate;
    
    my \pop1 = database.population_of('90210');
    my \pop2 = database.population_of('20500');
    say pop1 - pop2;
    

    Then the Rust:

    // Rust FFI Omnibus: Objects
    // http://jakegoulding.com/rust-ffi-omnibus/objects/
    
    pub struct ZipCodeDatabase {
        population: HashMap<String, u32>,
    }
    
    impl ZipCodeDatabase {
        fn new() -> ZipCodeDatabase {
            ZipCodeDatabase {
                population: HashMap::new(),
            }
        }
    
        fn populate(&mut self) {
            for i in 0..100_000 {
                let zip = format!("{:05}", i);
                self.population.insert(zip, i);
            }
        }
    
        fn population_of(&self, zip: &str) -> u32 {
            self.population.get(zip).cloned().unwrap_or(0)
        }
    }
    
    #[no_mangle]
    pub extern "C" fn zip_code_database_new() -> *mut ZipCodeDatabase {
        Box::into_raw(Box::new(ZipCodeDatabase::new()))
    }
    
    #[no_mangle]
    pub extern "C" fn zip_code_database_free(ptr: *mut ZipCodeDatabase) {
        if ptr.is_null() {
            return;
        }
        unsafe {
            Box::from_raw(ptr);
        }
    }
    
    #[no_mangle]
    pub extern "C" fn zip_code_database_populate(ptr: *mut ZipCodeDatabase) {
        let database = unsafe {
            assert!(!ptr.is_null());
            &mut *ptr
        };
        database.populate();
    }
    
    #[no_mangle]
    pub extern "C" fn zip_code_database_population_of(
        ptr: *const ZipCodeDatabase,
        zip: *const c_char,
    ) -> u32 {
        let database = unsafe {
            assert!(!ptr.is_null());
            &*ptr
        };
        let zip = unsafe {
            assert!(!zip.is_null());
            CStr::from_ptr(zip)
        };
        let zip_str = zip.to_str().unwrap();
        database.population_of(zip_str)
    }
    

    Obviously the C side of affairs will need to be quite different, but hopefully this gives enough clues.