flapigen-rs icon indicating copy to clipboard operation
flapigen-rs copied to clipboard

"not yet implemented" for ToString

Open spease opened this issue 7 years ago • 3 comments

foreign_interface!(interface ToString {
    self_type std::string::ToString;
    toString = std::string::ToString::to_string(&self) -> String;
});
thread 'main' panicked at 'not yet implemented', C:\users\spease\.cargo\registry\src\github.com-1ecc6299db9ec823\rust_swig-0.1.3\src\cpp\mod.rs:1303:18

Am I doing something wrong, or is this legitimately not possible? Thanks.

spease avatar Jun 14 '18 06:06 spease

Yes, at the moment it's impossible.

I implemented only support for callbacks that return nothing, because management of resources that gives C++ to Rust in such way is not clear.

Your example may be transformed by rust_swig to:

struct C_ToString {
  ....
      char * (*toString)(void *opaque);
};

namespace foo {
class ToString {
public:
  ...
  virtual std::string toString() const = 0;
private:
 static char * c_toString(void *opaque)
 {
        auto p = static_cast<ToString *>(opaque);
        auto std_string_ret = p->toString();
       //TODO:  reference to content of std_string_ret becomes invalid after return
      // should I allocate and copy and then free in Rust? 
 }
};

You see, the question is how to manage resources in ToString::c_toString: should I silently create an extra copy of std::string via malloc and force user to use add extern crate libc; to call free to deallocate memory in impl ToString for C_ToString and then allocate one more time for Rust String, or should I export to C/C++ Rust String allocator? However, it creates a tricky situation on C++ side where I need to somehow force user to use right allocator.

So you can write only:

foreign_interface!(interface ToString {
    self_type std::string::ToString;
    toString = std::string::ToString::to_string(&self, _: &str, _: i32, _: &[u32]);
});

almost every type is supported as an input, but none yet as an output for callbacks.

Dushistov avatar Jun 14 '18 08:06 Dushistov

I don't understand what you're saying. If a return type of String is specified, then the function contract is that it's returning an owned string object on the stack. In that case, the equivalent C++ function should have a return type of std::string or QString, not char *.

Even if memory allocation is needed to get the value out of Rust, then the generated function should be able to perform deallocation.

spease avatar Jun 18 '18 00:06 spease

Sorry, @spease I was busy with my day job, I use rust_swig there too, but for the last few days it was not the top priority.

I don't understand what you're saying.

May be you think about foreign_interface! as the way to call Rust code from C++?

If a return type of String is specified, then the function contract is >that it's returning an owned string object on the stack. In that case, the equivalent C++ function should >have a return type of std::string or QString, not char *.

Even if memory allocation is needed to get the value out of Rust, then the generated function should >be able to perform deallocation.

First of all to make things clear foreign_interface! is the way to callback C++/Java/Lang X code from Rust, not the other way around. So in your case foreign_interface!(interface ToString is the way to get Rust std::string::String from C++,

if you need to get a value out of Rust you need:

foreign_class!(class Fo {
    self_type Foo;
    method Foo::get(&self) -> String;
});

where struct Foo may look like struct Foo { imp: Box<ToString> }. In case of foreign_interface! it is supposed to work like this: Rust:

foreign_interface!(interface ToString {
    self_type std::string::ToString;
    toString = std::string::ToString::to_string(&self) -> String;
});

fn your_func(x: Box<ToString>) {
  let s =  x.to_string();
}

foreign_class!(class Boo {
 static_method your_func(x: Box<ToString>);
});

and on C++ side:

class MyImpl : public ToString {
public:
std::string to_string() override {  implementetion }
};

int main()
{
   auto callback = new MyImpl;
   auto c_struct_with_function_pointers = MyImpl::to_c_interface(callback);
   Boo::your_func(&c_struct_with_function_pointers);
}

Because of Rust being compatible only with C, to call ToString trait implemented on C++ side, you need (Rust):

impl ToString for CStructWithFunctionPointer {
  fn to_string(&self) -> String {
      let c_char_ptr: *const c_char = unsafe { (self.to_string)(self.cpp_this) };
      //convert CStr to String
     libc::free(c_char_ptr);
     //return String
  }
}

C++

char *c_to_string(void *this)
{
std::string s = static_cast<ToString>(this)->to_string();
char *c_char_ptr = malloc();
memcpy();
return c_char_ptr;
}

As you see, it is possible, but requires extra memory allocation, so it is impossible to work directly with C++, only via C.

One way to fix this is:

class MyImpl : public ToString {
public:
RustString to_string() override {  implementetion }
};

so C++ at start will work with Rust allocated string, but it requires extending my C++ implementation of RustString. Nobody asked about this before, but I will think about a way to implement this.

Dushistov avatar Jul 02 '18 15:07 Dushistov