#Correct way to deal with FFI function that takes owned value but not ownership

42 messages ยท Page 1 of 1 (latest)

eternal plinth
#

My memory is terrible and I already asked this ๐Ÿ˜

thorn shuttle
#

what is this supposed to mean? Do you have a non-ffi mock-up?

eternal plinth
#

Essentially the idea is to have T and a Borrowed<'_, T>.
An example function looks like:

fn safe_wrapper(array: &Array) {
    let chunks: ManuallyDrop<Array> = unsafe {
        (chunks as *const Array)
            .cast::<ManuallyDrop<Array>>()
            .read()
    };
    unsafe { my_ffi_func(chunks) }
}

I want to be able to provide a safer less ugly solution without wrapping every single type in some sort of borrowed version of the struct that is used in the FFI. For a few types this is fine but I don't want to expose and maintain a bunch of wrapper structs that hold a PhantomData for the lifetime. I can't think of a way to avoid solving this without unsafe or a bunch of boiler plate code (or macros).

#

Key part here is that T requires a specific alignment and size to pass it to the foreign function but doesn't actually take ownership of it. It only needs a byte copy.

I can't think of a way to do this without the mentioned problems.

#

My initial idea was to use something like

#[repr(C)]
struct BorrowedArray<'a> {arr: Array, phantom: PhantomData<&'a ()>}

Note that Array would be a byte copy of the value.

I want to avoid doing this for every type that is passed through FFI.

weary adder
weary adder
#

it's a de-facto owned T with a lifetime. Not sure why you'd need the lifetime if T is owned, though

eternal plinth
#

That solution is what I am trying out at the moment. It seams that it will introduce minimal boilerplate.

#

I can't implement Copy as the types require drop code and some are fairly large.

#

For example the Array struct above requires an allocation and its drop code to be called to avoid memory leaks, and so is many other types I need to do this with.

#

This seems to work fine but I am unsure if this could have unsoundness issues.

#
use std::{marker::PhantomData, mem::ManuallyDrop};

pub struct Borrowed<'a, T>(ManuallyDrop<T>, PhantomData<&'a T>);

impl<T> Borrowed<'_, T> {
    pub(crate) fn new(value: &T) -> Self {
        let val = unsafe { (value as *const T).cast::<ManuallyDrop<T>>().read() };
        Self(val, PhantomData)
    }
}

impl<T> AsRef<T> for Borrowed<'_, T> {
    fn as_ref(&self) -> &T {
        &self.0
    }
}

weary adder
#

I am increasingly more concerned as to what the heck you're doing

#

I guess Array wraps a pointer and the C takes this pointer?

#

The obvious thing to do here would be to access the pointer Array contains and pass that to C

eternal plinth
#

Ok so here is an example struct I actually use.

#[repr(C)]
pub struct KVec<T> {
    pub(super) len: usize,
    pub(super) capacity: usize,
    /// points to an allocation
    pub(super) ptr: *mut T,
}

So I want to pass this value via FFI as is with its exact layout and size, not just its pointer or a pointer to it. A literal byte copy of it. Since ptr points to an allocation and requires Drop::drop to be called I cannot implement Copy for it without leaking memory.

weary adder
#

This is fine to duplicate via ptr::read

#

Provided of course you only drop one of the copies

#

And don't keep the other one around afterwards

#

At which point I'm a bit confused as to what Borrowed is for

#

Can't you just read out of a reference?

eternal plinth
#

That would also work but ideally I want to place the unsafe call to a specific point and just call array.into() in the call sites.

weary adder
eternal plinth
weary adder
#

why

weary adder
#

Just do the copy in the methods that call into C

eternal plinth
#

Not sure I follow. The intention is the user provides a reference and I create a Borrowed<_> from it and pass it to the FFI call. From a users perspective they just pass a reference.

eternal plinth
#

Creating a byte copy via Borrowed::new and passing Borrowed<'_, T> to the foreign function.

weary adder
eternal plinth
#

Just to avoid boiler plate unsafe code. I have nearly 100 foreign functions and about 40 of them will require this so I just want to hide this logic away.

weary adder
#

You don't link it to the lifetime of value, so I can just write Borrowed::<'static, _>::new(value)

#

This is still sound since you also don't use that lifetime in the AsRef impl (not that you could). Provided you have no API that takes that lifetime seriously, you're fine

#

The actual unsoundness here is that you forgot #[repr(transparent)], but that's an easy fix

eternal plinth
#

Your right. Fixed the lifetime and repr.

#

So the final state of Borrowed looks like this.

use std::{marker::PhantomData, mem::ManuallyDrop};

#[repr(transparent)]
pub struct Borrowed<'a, T>(ManuallyDrop<T>, PhantomData<&'a T>);

impl<'a, T> Borrowed<'a, T> {
    pub(crate) fn new(value: &'a T) -> Self {
        let val = unsafe { (value as *const T).cast::<ManuallyDrop<T>>().read() };
        Self(val, PhantomData)
    }
}

impl<T> AsRef<T> for Borrowed<'_, T> {
    fn as_ref(&self) -> &T {
        &self.0
    }
}

I think this should be fully sound now.

weary adder
#

This seems sound, yes