FFI and raw pointers - what would you like to see in regards to utilities and containers?


#1

Currently I’m working on a crate to provide utilities for handling raw pointers, an often necessary evil in FFI-level code.

Quick little sketch of what I currently have:

struct Ptr<T, A: PtrAllocator<T>, C: PtrClone<T>, D: PtrDestructor<T>> { /* fields omitted */ }

Where PtrAllocator, et al, are traits for allocating a *mut T, cloning a *mut T, and freeing a *mut T respectively. There are reasonable “default” implementations of these traits.

Ptr<T> behaves a lot like a NonNull<T> (actually it uses NonNull internally).

What do you need from such a crate? Do you want reference counted containers? Atomic send/sync compatible containers? Something else?


#2

Fundamental types and methods are there (Box::into_raw(), CStr/CString), but they require boilerplate in each function on the C<=>Rust border.

So I’d love some proc macro that automagically wraps Rust functions like fn foo(x: &str, y: &[i32]) to be exposed as foo(char *, int *, size_t len) to C, and adds a prelude to the function that checks that args are non-NULL, strings are UTF-8, makes slices from pointer + length, etc.


#3

That’s a really interesting challenge! I’ll take a look at that.

I imagine the vice versa situation as well - stock rust types to C FFI types for C-ABI functions exported to Rust?


#4

Exposing Rust functions to C can be like magic because you get to work with Rust function signature with Rust types, and you can dictate how C will see them.

Exposing C functions to Rust as some safer, higher-level interface is a messy problem, because C function signatures are ambiguous. There’s no ownership information. It’s not even clear whether a pointer is for 1 thing or an array, so for every char * the user would have to manually specify whether it means CStr, CString, &[u8], CVec<u8>, etc. Not as auto-magic like interfacing the other way.


#5

There is already https://github.com/eqrion/cbindgen/ to use rust from C/C++, also there is my project for the same purposes https://github.com/Dushistov/rust_swig , the main difference is implicit (cbindgen) vs explicit (rust_swig), also there is https://github.com/rust-lang-nursery/rust-bindgen to do the opposite.


#6

I’m not sure that those actually solve kornel’s expressed need. What they talked about was not the C/C++ header needed for manually written extern “C” functions and constants, but:

So I’d love some proc macro that automagically wraps Rust functions like fn foo(x: &str, y: &[i32]) to be exposed as foo(char *, int *, size_t len) to C, and adds a prelude to the function that checks that args are non-NULL, strings are UTF-8, makes slices from pointer + length, etc.

They were referring to Rust-native functions, with safe, validated inputs, that has no idea if its being called across an FFI boundary, and “magic” taken to basically write an extern unsafe function that safely validates inputs from the FFI boundary, and then calls the native function with correctly validated data and types.

Consider this example:

#[arcana]
fn foo(&str)->&str { /*...substring? ... *./ }

Which would result in:

extern "C" {
    #[no_mangle]
    fn foo(p: *mut u8, len: usize, op: *mut *mut u8) -> usize {
        if p.is_null() { panic!(); }
        // make str via unsafe means
        let s_output = foo_r(us_input);
        op = s_output.as_ptr();
        let u = s_output.len();
        ::std::mem::forget(s_output);
        return u;
    }
}
fn foo(&str)->&str { /*...substring? ... *./ }

Am I wrong, kornel?


#7

@ZerothLaw

Actually my project do exactly what you describe (what I call explicit), if you have Rust function:

fn rust_function(_:&str) -> &str  { .... }

you write:

foreigner_class!(class Strings {
    static_method rust_function(_:&str) -> &str;
});

and got:

generated Rust code:

#[no_mangle]
pub extern "C" fn Strings_rust_function(a_0: *const ::std::os::raw::c_char)
 -> RustStrView {
    let mut a_0: &::std::ffi::CStr = a_0.swig_into();
    let mut a_0: &str = a_0.swig_deref();
    let mut ret: &str = rust_function(a_0);
    let mut ret: RustStrView = <RustStrView>::swig_from(ret);
    ret
}

generated C/C++ code:

struct RustStrView Strings_rust_function(const char * a_0);
class Strings {
...
public:

    static struct RustStrView rust_function(const char * a_0)
    {
        struct RustStrView ret = Strings_rust_function(a_0);
        return ret;
    }
};