Skip to content
Advertisement

PHP FFI – return array rom Rust function back to PHP

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 FFIParserException: 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

Advertisement

Answer

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
}
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement