t348575
t348575

Reputation: 753

Shared option-like serializable type with C/C++

I'm writing a rust library, and I have a struct (only my culprit field shown here). In many cases, I have actual serializable structs or other data being assigned to my_field, but occasionally I will have no data, and therefore want to assign null or no item.

I attempted to do this by returning like this (in functions that create these message structs): Message<[u8; 0]>, and i have ffi functions that return this type, which translates to Message<uint8_t[0]> as a return type for those functions in the generated header file (using cbindgen).

But, compiling a C++ program with array of size 0 gives the warning:

the size of an array must be greater than 0

What rust type / technique can I use to get around this? *const u8 is not serializable, Option does not exist in C++

#[repr(C)]
#[derive(Serialize, Deserialize)]
pub struct Message<T: Serialize> {
    pub my_field: T
}

Upvotes: 3

Views: 366

Answers (3)

Field
Field

Reputation: 459

Consider using Message<Option<&u8>>:

lib.rs:

use serde::{Deserialize, Serialize};

#[repr(C)]
#[derive(Debug, Deserialize, Serialize)]
pub struct Message<T: Serialize> {
    pub my_field: T,
}

#[no_mangle]
pub extern "C" fn get_my_field(msg: Message<Option<&u8>>) -> u8 {
    println!("{:?}", msg);
    println!("get_field {}", serde_json::to_string(&msg).unwrap());
    let a = msg.my_field.unwrap_or(&0);
    *a
}

#[no_mangle]
pub extern "C" fn new_message(my_field: Option<&'_ u8>) -> Message<Option<&'_ u8>> {
    Message { my_field }
}

lib.h generated by cbindgen:

#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>

template<typename T>
struct Message {
  T my_field;
};

extern "C" {

uint8_t get_my_field(Message<const uint8_t*> msg);

Message<const uint8_t*> new_message(const uint8_t *my_field);

} // extern "C"

main.cpp:

#include <lib.h>

int main()
{
    Message<const uint8_t *> msg;
    msg.my_field = NULL;
    printf("%d\n", get_my_field(msg));
    const uint8_t my_field = 1;
    msg.my_field = &my_field;
    printf("%d\n", get_my_field(msg));

    auto msg2 = new_message(NULL);
    printf("%d\n", get_my_field(msg2));

    auto msg3 = new_message(&my_field);
    printf("%d\n", get_my_field(msg3));

    return 0;
}

Output:

Message { my_field: None }
get_field {"my_field":null}
0
Message { my_field: Some(1) }
get_field {"my_field":1}
1
Message { my_field: None }
get_field {"my_field":null}
0
Message { my_field: Some(1) }
get_field {"my_field":1}
1

Upvotes: 0

Caesar
Caesar

Reputation: 8514

I think there are only two ways to get around this warning:

  • Turn off ISO C++ warnings. Iirc, ISO C++ simply doesn't allow zero-sized structures. (The best reference I can find for this right now is sizeof: "The result of sizeof is always nonzero")
  • Use some custom dummy struct for T that has a 1 byte sized memory representation. You can still make sure that the serialized representation has 0 size by skipping all fields (or using custom de-/serializers):
#[repr(C)]
#[derive(Serialize, Default)]
pub struct Nil {
    #[serde(skip)]
    _dummy: u8,
}

#[no_mangle]
pub extern "C" fn mk_Nil() -> Nil {
    Default::default()
}

Upvotes: 1

mibu
mibu

Reputation: 1537

Because type trait Deserialize is implemented on all types Option<T> given T implement Deserialize, you can use Option<T> to capture optional data. You can see documentation here

#[repr(C)]
#[derive(Serialize, Deserialize)]
pub struct Message<T: Serialize> {
    pub my_field: Option<T>
}

Upvotes: 0

Related Questions