Is it UB to use `&T` when a callback expects `*const T`?

I have a callback type in C which is:

extern "C" fn disk_unmounted(disk: DADiskRef, ctx: *const T);

The callback is actually written in Rust but called by OSX Disk Arbitration API.

Now, i want to pass a &T as the ctx instead of *const T (which means i have to dereference it myself).

I can change the type of the callback to

extern "C" fn disk_unmounted(disk: DADiskRef, ctx: &T);

but will this cause any UB?

tl;dr:
can &T be used in place of *const T and &mut T in place of *mut T while doing FFI?

This should be fine as long as the invariants of &/&mut references are upheld by the C side.

2 Likes

Well, it depends. If C code tried to call that method with a null pointer, then that's immediate UB because references can't be null.

2 Likes

Well, all the code is in Rust and i'm sure the reference will be non-null as i'm passing the reference to the callback myself.

the callback handler is similar to this

extern "C" fn mount_handler(disk: DADiskRef, ctx: &Arc<RwLock<bool>>) -> DADissenterRef {
    let allow = ctx.try_read().unwrap().clone();

    if allow {
        ptr::null()
    } else {
        let msg = CFString::new("Mounting of devices is not allowed");
        let maybe_res = unsafe {
            DADissenterCreate(CFAllocatorGetDefault(), -119930876, msg.as_concrete_TypeRef())
        };

        return maybe_res;
    }
}

and the way the callback is registered:

let allow = self.allow_mount.clone(); // type is Arc<RwLock<bool>>

unsafe {
    DARegisterDiskMountApprovalCallback(
        session,
        kDADiskDescriptionMatchVolumeMountable,
        mount_handler,
        &allow,
    );
}

the actual function declaration of DARegisterDiskMountApprovalCallback is:

void DARegisterDiskMountApprovalCallback(DASessionRef session, CFDictionaryRef match, DADiskMountApprovalCallback callback, void *context);

which i modified in Rust:

extern "C" {
    fn DARegisterDiskMountApprovalCallback(
        session: DASessionRef,
        r#match: CFDictionaryRef,
        callback: DADiskMountApprovalCallback,
        ctx: &Arc<RwLock<bool>>,
    );
}

i do get the lint

warning: `extern` block uses type `Arc<RwLock<bool>>`, which is not FFI-safe

If the performance is not absolutely critical why not use Option<&T> instead of &T? This is guaranteed to have the same layout but if through some bug you got a null pointer you would panic in a predictable way.

2 Likes

You should absolutely keep the extern block as the correct function definition and create a helper function which takes your &Arc and performs the cast.