0

I'm trying to pass an array of strings from a rust library to a nodejs program via FFI. I followed an example from Create struct in C/Rust code for Node-FFI but can't quite get it to work. It works fine with a single element in the array, but if I add 2 elements I get a Segmentation Fault trying to read the second element.

Rust code:

#[repr(C)]
pub struct Config {
    pub value: String,
}

#[repr(C)]
pub struct State {
    pub configs: *mut Config,
    pub num_configs: usize,
}

#[no_mangle]
pub extern "C" fn fetchData() -> *mut State {
    let mut ffi_items: Vec<Config> = vec![];
    ffi_items.push(Config {
        value: String::from("Hello"),
    });
    ffi_items.push(Config {
        value: String::from("World"),
    });

    return Box::into_raw(Box::new(State {
        configs: Box::into_raw(ffi_items.into_boxed_slice()) as *mut Config,
        num_configs: 2,
    }))
}

NodeJS code:

const ref = require('ref-napi')
const ffi = require('ffi-napi')
const path = require('path')

const ArrayType = require('ref-array-di')(ref)
const StructType = require('ref-struct-di')(ref)

console.log('NODE: Running...')

const Config = StructType({
  value: ref.types.CString,
});
const State = StructType({
  configs: ArrayType(Config),
  num_configs: ref.types.size_t,
})
const StatePtr = ref.refType(State);

const libPath = path.join(__dirname, '../target/release/libfetch.dylib')
const lib = ffi.Library(libPath, {
  fetchData: [ StatePtr, [] ]
});

const dataRef = lib.fetchData()
const data = dataRef.deref()

data.configs.length = data.num_configs

console.log(`NODE: data.configs.length = ${data.configs.length}`)

console.log(`NODE: data[0] = ${data.configs[0]?.value}`)
console.log(`NODE: data[1] = ${data.configs[1]?.value}`)

Console output:

$ node src/index.js 
NODE: Running...
NODE: data.configs.length = 0
NODE: data[0] = Hello
Segmentation fault: 11

Versions:

$ rustc --version
rustc 1.59.0 (9d1b2106e 2022-02-23)
$ node -v
v16.15.0

Running this on mac os 12.3.1

Alexey
  • 1
  • 2

1 Answers1

0

Figured it out, was an issue with the strings, not the array. I wasn't properly handling them so as far as I understand the memory for the strings was cleaned up after the rust function returned, wrapping them in a Box solved the problem:

Rust diff:

3c3
<     pub value: String,
---
>     pub value: *mut String,
16c16
<         value: String::from("Hello"),
---
>         value: Box::into_raw(Box::new(String::from("Hello"))),
19c19
<         value: String::from("World"),
---
>         value: Box::into_raw(Box::new(String::from("World"))),

Node diff:

11c11
<   value: ref.types.CString,
---
>   value: ref.refType(ref.types.CString),
31,32c31,32
< console.log(`NODE: data[0] = ${data.configs[0]?.value}`)
< console.log(`NODE: data[1] = ${data.configs[1]?.value}`)
---
> console.log(`NODE: data[0] = ${data.configs[0]?.value.deref()}`)
> console.log(`NODE: data[1] = ${data.configs[1]?.value.deref()}`)
Alexey
  • 1
  • 2
  • I'm not sure if you're doing this manually for some unspecified reason, but the crate [neon](https://crates.io/crates/neon) provides safe bindings for node and is decent to work with from my experience. – Ian S. May 21 '22 at 00:50