Sorry for a silly question: Is there a better way to avoid `private_interfaces`?

TL;DR:

Is it possible to prevent user use a type T in ffi interfaces, while allowing user use T elsewhere?

I means, since T might have the same #[repr] as U but with Drop implemented, users may wrote extern "C" fn (U) to extern "C" fn (T) accidently, how to prevent it?

To avoid a XY problem, the background of such silly question is provided as follow:

This could be a ffi problem, which occurs in my rmin crate.

The crate, rmin, are designed to help with R-plugin writting. The basic FFI interface could be something like:

extern "C-unwind" fn (a:SEXP)->SEXP { ... }

According to R documents,

  1. The parameter R passed in is read-only.
  2. Each time call R function, the allocated variable might be recycled by R unless we protected it.
  3. Before the program return, we must unprotect all of the allocated variables.

With these restrictions,

  1. SEXP<T> is a readonly type that does not impl IndexMut, and have the same repr as SEXP
  2. Owned<T> is a owned type thus it impls IndexMut, and have the same repr as SEXP
  3. Protected<T> is a protected Owned<T>, we must not send nor receive Protected<T> from ffi interface.

With these 3 types, we could wrote a healthy function like

extern "C" fn add(a:SEXP<f64>, b:SEXP<f64>)->Owned<f64> {
    let mut c=Owned::new(1);
    c[0]=a[0]+b[0]; // I have `Index` and `IndexMut` implememted
    c
}

The question is, how to achieve 3. gracefully? I means, how to prevent users wrote things like

extern "C" fn add(a:SEXP<f64>, b:SEXP<f64>)->Protected<f64> {
    let mut c=Protected::new(1);
    c[0]=a[0]+b[0]; // I have `Index` and `IndexMut` implememted
    c // currently, Protected<f64> is send back to R, without calling its `drop` function, which breaks 3.
}

Currently, I tried the visibility, wrote things like:

#[repr(transparent)]
pub struct Owned<T:RType>{
    sexp:libR::SEXP,
    _marker:[PhantomData<T>;0]
}
impl<T:RType> Owned<T> {
    #[allow(private_interfaces)]
    pub fn protect(self)->Protected<T> {
        self.into()
    }
}
mod protected {
    /// it seems Protected have no doc.
    #[repr(transparent)]
    pub struct Protected<T:RType>{
        sexp:libR::SEXP,
        _marker:[PhantomData<T>;0]
    }
}

Although protect interface pub fn protect(self)->Protected<T> generate no warning about private_interfaces, there is no doc for the Protected type. What makes it worse, we cannot write things like let a=Protected::new(1), and we have to write let a=Owned::new(1).protected().

What could be the best way for hiding such struct?


Update: with macro 2.0, we can play with hygiene macro which prevent writting Protected<T> accidently, without removing its documents.

Just use such grammar could be fine:

// the redundant `Key1=` is a checker to show whether we use the correct parameter.
macro visible_controller(Key1=$Key1:tt Key2=$Key2:tt ...) {
    // things with $Key will be visible outside
    struct $Key1();
    // things without $ will be invisible
    struct Protected();
}
visible_controller!(Key1=Key1 Key2=Key2);
// Protected only exists in the doc, but keep invisible from users.

Note that making Protected a private type doesn't actually prevent the authors of FFI functions from returning it. Since it's repr(transparent), they can just transmute it into a SEXP and return it.

The only way you can reliably prevent using such a type would be to make it completely impossible to obtain an instance of.

This is trivially fixed by providing a convenience method Owned::protected: fn(T) -> Protected<T>.

This could be documented in the doc, and we could provide a .into() method to simply avoid the transmute. What's more, a transmute is really unsafe, but a extern function call is just... safe.

#[no_mangle]
extern "C" fn dangerous(input:HasDrop)->HasAnotherDrop {
    // the input we drop might be a object from ffi
    // and we send `HasAnotherDrop` back and prey the ffi call could drop it
    // I just wonder why it is SAFE...
}

Thus I want to try my best to prevent the misuse.

As for Owned::protected, you're correct, but the doc is still missing. Should I add all the doc related to Protect<T> to Owned<T>?

It's not, because Rust can't check the safety of arbitrary external code. However, I don't get how this is related to any of what I wrote. You are talking about providing an into() method, but that's exactly what you shouldn't provide, since you don't want to allow returning a Protected – wasn't that the whole point?

Furthermore, no matter what conversion methods you do or do not provide, the implementor of an FFI function can always transmute a Protected into anything else of the same size, so you can't prevent this.

Using the into method is sound, since:

impl<T:RType> From<Protected<T>> for Owned<T> {
    fn from(p:Protected<T>) {
        Self{sexp:p.sexp, _marker:p._marker}
        // p is dropped here, thus the Owned<T> could be safely returned.
    }
}

This requires an unsafe block, thus it is OK to alert users who write such code.

Further, transmute Protected<T> to Owned<T> seems safe (since only memory leak occurs).
the unsafe thing is that transmute Owned<T> to Protected<T>, this is prevented since Protected<T> is not visible.

I thought you were talking about the opposite direction.

No, this is just wrong.

Rust has type inference. If you allow the Protected type to leak into any public API, a suitably-chosen transmute will have its second type parameter inferred as Protected.

Example.

1 Like

Yes, I realized that. With unsafe, we could do many evil things.

Thus I propose a Pre-RFC, to ensure some unsafe mark is marked

it is something like

extern fn if_you_really_want_this_dangerous_guy(unsafe foo:Protected impl Drop) -> unsafe Protected impl Drop
/// or
unsafe extern fn if_you_really_want_this_dangerous_guy(foo:Protected) -> Protected // use it at your own risk.

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.