I'm attempting to create a binding to Linux's libtraceevent library. Bindgen has generated the bindings correctly and I'm attempting to write some tests in order to guide me to the design of a safe API on top of the bindgen generated code.
A particular function expects a *mut *mut tep_handle argument as shown below:
You are passing the address of a temporary. Declare the pointer as a variable instead, and pass its address directly.
See, you shouldn't create a value of type tep_event. The double-pointer pattern in C is usually meant for functions that allocate a heap-allocated object and "return" a pointer to it by writing through the outer pointer (since the real return value of the function is used for something else, likely an error code).
It's not the null, you could have initialized the pointer with literally anything, because it will be overwritten. It's the fact that you passed the address of an existing variable (and not a temporary) that makes it work.
By the way, are you sure that the handle can be null? It'd be necessary to read the documentation for this function. If it's an input parameter, then it's likely expected to point to a valid object.
By the way the correct direct translation of this would have been to have a let mut tep_event = MaybeUninit::<*mut teo_event>::uninit();, then pass tep_event.as_mut_ptr() to the function call. The function initializes the value in the MaybeUninit, so afterwards, you're allowed to call tep_event.assume_init() to obtain the pointer of type *mut tep_event that was (logically) returned.
Of course, initializing the pointer with something like null is syntactically a bit lighter, but that makes sense since it avoids the need to manually work with uninitialized data. Uninitialized variables in Rust can never be accessed (e. g. read, or referenced, i. e. borrowed), even in unsafe code, so there is some overhead in how the MaybeUninit might become necessary to use in unsafe cases. But of course, the situation in C (and even more so in C++) is actually worse, because it's way too easy to skip initialization there.
That's only almost universally true. For MaybeUninit<T> it's actually false if T is a zero-sized type. Similarly, I believe it should also be false if the type is allowed to be completely uninitialized, or does (or can) consist entirely of padding. And this is the case here because, AFAICT (and presumably accidentally), MaybeUninit<MaybeUninit<tep_event>> is used, the inner type MaybeUninit<teo_event> is allowed to be completely uninitialized.
Yes, but I'm not talking about that – it's clearly an out argument. I'm talking about the handle, which is the first argument. I don't see it declared in the quoted snippets, so I don't know how it's initialized. I was just trying to raise awareness about this being another potential pitfall, anyway.
Typically, one would probably put it into a new variable, so assume_init only needs to be called once, which makes the unsafe code usage easier to review. The new variable even could use the same name and shadow the previous one.
Also, in case tep_parse_format needed to be called in multiple places, a nice approach would be a wrapper function around the tep_parse_format function that could handle this stuff, make sure that only when there is no error that could have precluded the initialization, assume_init is called, and maybe returns a more rusty Result return type containing the *mut tep_event.