Guidance on how to pass null for a pointer to a pointer

I've not done much rust so i've jumped off at the deep end to by doing some FFI C calls. I'm unsure of the correct way to make one of my calls.

I have a C function which has the signature something like
void f(char** buf);
In C you can do these two calls.

char* buf;
f(&buf); // Function returns a pointer to something and does something
f(NULL); // Function does something but doesn't return a pointer.
  1. What would be the correct signature for this function in rust? I hope I don't embarrass my self but I have this. fn f(buf : &*mut libc::c_void);?
  2. How should I do the two different calls?
let mut buf : [i8; 5] = [1,2,3,4,5];
unsafe{f(& buf.as_mut_ptr());}  // seems to work but I'm not entirely sure it is correct.

unsafe{f(& *ptr::null_mut());}   // I get the warning 'this code causes undefined behavior when executed'

The issue is that this dereferences a null pointer. If you remove the * the warning disappears.

Then in both calls you use a function that returns a pointer (as_mut_ptr and null_mut) and then take a reference to that returned pointer.

I avoid & and &mut in C FFI function signatures, since those are Rust things. So you could make the signature something like:

fn f(buf: *mut *mut c_void)

For the signature you gave: void f(char** buf);, the rust extern signature is a pretty mechanical translation, for pointers you can nearly just flip them around, you just need to explicitly say they are * mut (as opposed to * const)

So your example void f(char** buf); would translate to:

extern "C" {
  fn f(buf: *mut *mut c_char);
}

This translation is so mechanical that you probably want to look into using bindgen for anything serious.

References are not "FFI-safe" (they don't have a guaranteed ABI on the platform like pointers do, at of you use them you should be getting a warning about it. As a rule of thumb, primitive number types like u32 and f64, anything in std::ffi or std::ptr, and your types with an explicit layout like #[repr(C)] are good.

Also remember to match any types: in C char, int etc might end up as different sizes or even signed-ness, so be sure use the equivalent in std::ffi - or use explicitly sized types on the C side.

3 Likes

in some forums, someone said “write in C , or rewrite in rust, don’t binding” can you give some perspective about this?

It seems a bit silly as a general statement. There's are plenty of good crates that are bindings to an existing C library.

Perhaps they had some more specific meaning in that context? For example if the reason you're using Rust is because you want to make some existing C code safe and secure, adding a Rust wrapper does nothing to help and can easily make things worse.

2 Likes

This is not true.

Pointers and references have the same layout. Mutability of the pointer or reference does not change the layout.

— https://doc.rust-lang.org/reference/type-layout.html#pointers-and-references-layout

In fact, Option<&T> has the same memory representation as a nullable but aligned pointer, and can be passed across FFI boundaries as such.

— https://doc.rust-lang.org/std/primitive.reference.html

You may be thinking of the fact that “wide” references to dynamically sized types do not have a guaranteed ABI — but this is equally true of raw pointers to such types.


In my opinion, you can and should use references in FFI function signatures when, if you didn't, the thing you'd be doing anyway is immediately converting to or from a reference. In that case, putting the reference in the signature is no more fragile, reduces the complexity of the program, and removes an opportunity for doing the conversion wrong.

4 Likes

Happy to be corrected!

I cannot believe I didn’t just try this. It seems quite obvious now that I see it. Thanks

However If You use the Null Pointer If You Try to Deference it in C UB in Rust Panic Possible

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.