Hmm, the error says enum has no representation hint so have you tried wrapping your return value in a struct and setting an repr? Perhaps something like this:
Please, just stick to the correct C function (or use bindgen which does it for you) and then use a wrapper which converts the pointer to an Option, if you want.
Else it may cause a very hard to debug error, which can cost you a lot of time.
It may be wise to just follow things people have done but I still want to understand why people do this and not do that. Because I think that there is a conflict in my understanding about Option over FFI, I just want to know which side is not correct.
Therefore Option is not FFI safe and should not be used (ultimatly because Rust ABI is not stable (nor really specificied) it is not FFI safe. (Even C++s ABI is not specified. IIRC only C has a stable ABI, and that's why it is the lingua franca).
As I understand it, the compiler is (in principle) free to optimize your types in whatever way it likes so long as it doesn't break Rust code. So it may have one memory layout today but that could change in a future version.
When you pass it over FFI you need to tell the compiler to use an explicit memory layout so that it can't optimize the type in ways that break foreign functions.
The easiest way to do this is to use primitive types with a known layout. You can then have a wrapper function that converts to Rust types. Which might be a no-op anyway after its optimized.
In this specific scenario, there is a std::ffi::CStr for dealing with C strings which has a from_ptr constructor that takes a *const c_char. You really should write a wrapper function that returns a &CStr here.
I think this is the problem, because for nullable pointer optimization, it's required that T is a type which is never null, but more concretely
Certain Rust types are defined to never be null . This includes references ( &T , &mut T ), boxes ( Box<T> ), and function pointers ( extern "abi" fn() )... However, the language provides a workaround.
... an enum is eligible for the "nullable pointer optimization" if it contains exactly two variants, one of which contains no data and the other contains a field of one of the non-nullable types listed above.
Though NonNull<T> is never null, it is not included in these types (i.e. &T, &mut T, ....) then the compiler gives warning about that. I can confirm that the following:
Yes, I stumbled upon this warning about Option<NonNull<T>> not being FFI-safe a few days ago, and I concur that it is weird and inconsistent.
When T : Sized, NonNull<T> is guaranteed to have one niche: the null bit pattern (0_usize). Therefore enum layout optimization makes Option<NonNull<T>> be guaranteed to map None to this null bit pattern, and Some(x) to x.
Given the current warning, and while waiting for it to be fixed, you can disable the lint on that signature (#[allow(improper_ctypes)]), and have the following guard around:
extern "C" {
#[allow(improper_ctypes)]
fn foo () -> Option<NonNull<u8>>;
}
/// Checks that Option<NonNull<u8>> and usize have the same size,
/// and that None is null.
#[deny(const_err)]
const _GUARD: () = [()][unsafe {
type Src = Option<NonNull<u8>>;
type Dst = usize;
let _: [();
::core::mem::size_of::<Src>()
] = [();
::core::mem::size_of::<Dst>()
];
union Transmute {
src: Src,
dst: Dst,
}
Transmute { src: None }.dst
}];