PHP → Rust: decode a JSON via FFI

Hello everybody!

I'm trying to decode a JSON in Rust and return the decoded value to PHP by using FFI.

Here is what the code looks like at the moment:

use std::ffi::{c_char, CStr, CString};

#[no_mangle]
pub extern "C" fn decode(json: *const c_char) -> *const c_char {
    let json = unsafe { CStr::from_ptr(json) }
        .to_str()
        .unwrap();

    let decoded: Result<&str, serde_json::Error> = serde_json::from_str(json);
    
    return CString::new(decoded.unwrap()).unwrap().into_raw()
}

and this is how it gets called by PHP:

$ffi = FFI::cdef('const char* decode(const char* json);', __DIR__ . '/libjson_ffi.dylib');

return $ffi->decode('"abc"');

this works as long as the JSON to be decoded only contains a string, e.g. '"abc"'. However if I try to decode an object, e.g. '{"foo":123}' it throws this error as decoded.unwrap() is no longer a borrowed string:

thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Error("invalid type: map, expected a borrowed string", line: 1, column: 0)', src/lib.rs:11:33
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fatal runtime error: failed to initiate panic, error 5

My questions at this point are: how do I define a function that can return any type (depending on the JSON content) and make the FFI call work? Should I define a union as return type? Would PHP be able to interpret the union?

Thank you in advance for any help!

No, bare unions don't contain type information.

For starters, use the Value enum from serde_json for decoding into an effectively dynamically-typed value.

Then, you'll have to figure out how you can convert the different variants of Value into the corresponding PHP objects (probably zval). You can get started using this tutorial.

Thanks @H2CO3 for the quick answer :slight_smile:

If I understood well, you are suggesting to return serde_json::Value in the Rust function - something like:

use std::ffi::{c_char, CStr};
use serde_json::{Value, from_str};

#[no_mangle]
pub extern "C" fn decode(json: *const c_char) -> Value {
    let json = unsafe { CStr::from_ptr(json) }
        .to_str()
        .unwrap();

    from_str(json)?
}

How would the C function signature differ when defined in FFI? At the moment it is:

const char* decode(const char* json);

but I wouldn't know how to redefine it to return the serde_json::Value :thinking:

Thanks again for your help!

No. Again, that's not something PHP can interpret, and it's not FFI safe (basically nothing except the most primitive types, such as ints, floats, and pointers are FFI-safe, with a few very special exceptions).

Once you have the Value inside the function, you have to convert it to a PHP object. What precisely the signature must be and how values should be "returned" must be determined based on what the PHP_FUNCTION macro expands to. I wouldn't necessarily expect the return value of the C function to be the return value of the corresponding PHP function.

But you should probably delegate all of this highly unsafe work to an existing crate such as ext-php-rs.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.