PHP FFI - return array rom Rust function back to PHP

Viewed 402

I need to return few values from rust function. Tried to declare function which returns an array

$ffi = FFI::cdef('float get_arr()[2];', './target/release/libphp_rust.dylib');

$array = $ffi->get_arr();

But got an error: PHP Fatal error: Uncaught FFI\ParserException: function returning array is not allowed at line 1 in /array.php:3

It seems PHP FFI can't work with arrays directly. So I found another solution. I created C-array from PHP, then passed pointer to it to Rust code and then populated it with Rust function:

$ffi = FFI::cdef('bool get_arr(float (*res)[2]);', './target/release/libphp_rust.dylib');

$array = $ffi->new('float[2]');

$result = $ffi->get_arr(FFI::addr($array));

if ($result) {
    var_dump($array);
} else {
    //... something went wrong
}
#[no_mangle]
pub extern fn get_arr(array_pointer: *mut [f32;2]) -> bool {
    let res = unsafe {
        assert!(!array_pointer.is_null());
        &mut *array_pointer
    };

    res[0] = 0.1;
    res[1] = 0.2;

    return true;
}

This solutions seems to work correct but i have some doubts about it:

  1. Is passing pointers to FFI safe enough and what problems may I face with this in future?
  2. Are Rust arrays fully C-compatible so that I'm able to assign value to it directly by index?
  3. I there better way to achieve what I need? Or maybe are there some good practices about passing complex data structures with FFI?

Thanks

1 Answers

The rules surrounding this are still up in the air, so your example is questionably safe. This should be ok, but requires nightly features:

#![feature(maybe_uninit_extra)]
#![feature(ptr_as_uninit)]

// Make sure you use `extern "C"`. `extern` alone means `extern "Rust"`.
#[no_mangle]
pub extern "C" fn get_arr(array_pointer: *mut [f32; 2]) -> bool {
    let fat: *mut [f32] = array_pointer;
    let res = unsafe { fat.as_uninit_slice_mut().unwrap() };

    res[0].write(0.1);
    res[1].write(0.2);

    true
}

On the stable channel it's just less elegant:

// Make sure you use `extern "C"`. `extern` alone means `extern "Rust"`.
#[no_mangle]
pub extern "C" fn get_arr(array_pointer: *mut [f32; 2]) -> bool {
    assert!(!array_pointer.is_null());
    unsafe {
        let res = array_pointer as *mut f32;
        res.add(0).write(0.1);
        res.add(1).write(0.2);
    }

    true
}
Related