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

231 views Asked by At

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.

3

There are 3 answers

1
librasteve On BEST ANSWER

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.

4
Håkon Hægland On

it would be very convenient for me to be able to return a Raku Hash from an exported function

A workaround could be to let the C function return a struct with key and values and then write a Raku wrapper that converts that into a Raku hash like this:

use v6;
use NativeCall;

constant LIB  = ('./libmylib.so');

class HInfo is repr('CStruct') is export {
    has Str $.key1;
    has num64 $.value1;
    has Str $.key2;
    has num64 $.value2;
}

sub foo_(Str--> HInfo) is native(LIB) is symbol('foo') { * }
sub foo(Str $str --> Hash) {
    my HInfo $hinfo = foo_($str);
    my %h;
    %h{$hinfo.key1} = $hinfo.value1;
    %h{$hinfo.key2} = $hinfo.value2;
    return %h;
}

my %h = foo("bar");
dd %h;
1
Xliff On

As someone suggested, this is best done with a wrapper function. First things first though, what kind of value are you returning from C?

Your best bet is to return a CStruct.