9
votes

Previously when the code-base was in C++, I had C++ wrapper files which would link to the code base and I would run swig (version 3 for C++11 support) to generate the interface files for the target language (Python, JavaScript, C#, etc.). Then of course get all these files and libraries compiled into a shared object and have it invoked from the required languages. Now the code base is being changed to rust. So for swig to work I have the following:

  1. Main rust code file compiling into an rlib.
  2. Rust wrapper file that calls into the main code base but uses no_mangle and extern syntax for FFI and compiles into a staticlib.
  3. A C file that calls the rust wrapper and is a replica of it.

Now I use swig on the C file, get the interface file for the target language, combine all the files (steps two and three) and the SWIG interface file) into a shared object and call from the target language.

So:

  1. Is the approach fine?

  2. I can get free functions to work. However I'm confused on how to get member functions (methods) to work. In C++ the first parameter of the member functions is the implicit this pointer. So I could return a void* handle to the class or struct to the C interface which would pass it on to others who wanted to store it (e.g. jsctypes for Firefox) and then on receiving again reinterpret_cast it to the concrete/actual type and invoke the member function on it. How do I do this with Rust?

e.g., for

pub struct A { id: SomeType, }
impl A {
    pub fn some_funct_0(&mut self) {}
    pub fn some_funct_1(&self) {}
}

impl SomeTrait for A {
    fn some_trait_funct(&mut self) {}
}

So how do I access these member functions on an object of A (should be unmanaged and on the heap I guess?) from target languages (Python, C, etc.) or even simply a C interface?

2
In method definitions, self is sugar for self: Self, &self for self: &Self and &mut self for self: &mut Self. Sure, the self keyword is still special in Rust, but A::some_trait_funct and A::some_funct_0 are still functions with &mut A as their first parameter’s type.Chris Morgan
You could try to create your own attribute through a compiler-plugin, which automatically inserts no_mangle, creates a c-header file for that one function and runs swig on it.oli_obk

2 Answers

6
votes

Well, methods are just regular functions, and as Chris said, self argument has implicit connection with Self type. With your example (slightly modified) using functions from C code should be straightforward:

#[repr(C)]
pub struct A { id: u32, }

#[no_mangle]
pub extern fn new_a(id: u32) -> A {
    A { id: id }
}

impl A {
    #[no_mangle]
    pub extern fn some_funct(&self) {
        println!("Called some_funct: {}", self.id);
    }
}

trait SomeTrait {
    extern fn some_trait_funct(&self);
}

impl SomeTrait for A {
    #[no_mangle]
    extern fn some_trait_funct(&self) {
        println!("Called some_trait_funct: {}", self.id);
    }
}

Note that I added extern to change calling convention and #[no_mangle] to avoid name mangling and #[repr(C)] on the struct. The latter is not necessary if your code creates Boxes of the struct and pass them to C as raw pointers. I'm not sure, however, how #[no_mangle] could affect trait methods if there is more than one trait implementor - if both have #[no_mangle], there is bound to be some kind of name conflict.

Now using this type and its functions from C is easy:

#include <stdint.h>

struct A {
    uint32_t id;
};

extern struct A new_a(uint32_t id);
extern void some_funct(const struct A *self);
extern void some_trait_funct(const struct A *self);

int main() {
    struct A a = new_a(123);
    some_funct(&a);
    some_trait_funct(&a);
}

This program compiles and works:

% rustc --crate-type=staticlib test.rs
multirust: a new version of 'nightly' is available. run `multirust update nightly` to install it
note: link against the following native artifacts when linking against this static library
note: the order and any duplication can be significant on some platforms, and so may need to be preserved
note: library: System
note: library: pthread
note: library: c
note: library: m
% gcc -o test_use test_use.c libtest.a -lSystem -lpthread -lc -lm
% ./test_use
Called some_funct: 123
Called some_trait_funct: 123

If methods accepted &mut self:

#[no_mangle]
extern fn some_funct_mut(&mut self) { ... }

you would need to omit const:

extern void some_funct_mut(struct A *self);

If methods accepted self:

#[no_mangle]
extern fn some_funct_value(self) { ... }

you would need to pass the structure by value:

extern void some_funct_value(struct A self);

Though if you use the structure through an opaque pointer, calling functions taking it by value may be difficult as C has to know the exact size of the structure. Not that it is that common with opaque pointers, I believe.

5
votes

Ok, as i commented in the accepted answer that I couldn't use that approach, I ended up doing something like this for other's to comment on:

The backend rust code that gets compiled to rlib:

pub trait TestTrait {
    fn trait_func(&mut self) -> i32;
}

pub struct TestStruct {
    value: i32,
}

impl TestStruct {
    pub fn new(value: i32) -> TestStruct {
        TestStruct {
            value: value,
        }
    }

    pub fn decrement(&mut self, delta: i32) {
        self.value -= delta;
    }
}

impl TestTrait for TestStruct {
    fn trait_func(&mut self) -> i32 {
        self.value += 3;
        self.value
    }
}

The rust-wrapper over this that links to the above rlib and compiles into staticlib (ie., .a in Linux etc):

#[no_mangle]
pub extern fn free_function_wrapper(value: i32) -> i32 {
    rustlib::free_function(value)
}

#[no_mangle]
pub extern fn new_test_struct_wrapper(value: i32) -> *mut libc::c_void {
    let obj = rustlib::TestStruct::new(value);
    unsafe {
        let raw_ptr = libc::malloc(mem::size_of::<rustlib::TestStruct>() as libc::size_t) as *mut rustlib::TestStruct;

        ptr::write(&mut *raw_ptr, obj);
        raw_ptr as *mut libc::c_void
    }
}

#[no_mangle]
pub extern fn test_struct_decrement_wrapper(raw_ptr: *mut libc::c_void, delta: i32) {
    unsafe {
        mem::transmute::<*mut libc::c_void, &mut rustlib::TestStruct>(raw_ptr).decrement(delta);
    }
}

#[no_mangle]
pub extern fn test_struct_trait_function_wrapper(raw_ptr: *mut libc::c_void) -> i32 {
    unsafe {
        mem::transmute::<*mut libc::c_void, &mut rustlib::TestStruct>(raw_ptr).trait_func()
    }
}

The C-wrapper (api.h & api.c) that links to the staticlib above and compiles into a shared object if required:

extern int32_t free_function_wrapper(int32_t value);

extern void* new_test_struct_wrapper(int32_t value);
extern void test_struct_decrement_wrapper(void* ptr, int32_t delta);
extern int32_t test_struct_trait_function_wrapper(void* ptr);

int32_t free_function(int32_t value) {
  return free_function_wrapper(value);
}

void* new_test_struct(int32_t value) {
  return new_test_struct_wrapper(value);
}

void test_struct_decrement(void* ptr, int32_t value) {
  test_struct_decrement_wrapper(ptr, value);
}

int32_t test_struct_trait_function(void* ptr) {
  return test_struct_trait_function_wrapper(ptr);
}

Now just run SWIG over the C file (i've posted just the .c file - you can guess the .h over which SWIG will run) for the target language, get an interface_wrap.c generated (default name) by it and compile these source code link, them against the staticlib to get a shared-object.

Eg, for python:

swig -python interface.i
gcc -std=c99 -c -fPIC -Wall -Werror -O2 api.c interface_wrap.c -I/usr/include/python2.7
gcc -shared -o _port_sample.so api.o interface_wrap.o -L./ -lrust_wrapper

Now just call from Python and the whole thing works:

$ python
Python 2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import port_sample
>>> handle = port_sample.new_test_struct(36)
>>> port_sample.test_struct_decrement(handle, 12)
>>> value = port_sample.test_struct_trait_function(handle)
>>> print value
27
>>> exit()

I hope someone finds this useful and/or can suggest improvements etc. I've also got this thing working and committed to my github repo: https://github.com/ustulation/rust-ffi/tree/master/python-swig-rust