I'm writing a foreign function interface (ffi) to expose the API of a pre-existing C++ library to some new Rust code I am writing. I am using the Rust cxx
module for this.
I am running into some problems related to Pin
(a topic which I have to admit I don't have a complete grasp of).
My C++ module has an API that exposes pointers to some contained objects from the main object that owns these contained objects. Here is a contrived example:
// test.hpp
#include <string>
#include <memory>
class Row {
std::string row_data;
public:
void set_value(const std::string& new_value) {
this->row_data = new_value;
}
};
class Table {
Row row;
public:
Table() : row() {};
Row* get_row() {
return &this->row;
}
};
inline std::unique_ptr<Table> make_table() {
return std::make_unique<Table>();
}
The idea is that you create a Table
object, which then allows you to obtain a pointer to it's Row
so you can manipulate it.
My attempt to create a Rust FFI looks like this:
// main.rs
use std::pin::Pin;
use cxx::let_cxx_string;
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("src/test.hpp");
type Table;
pub fn make_table() -> UniquePtr<Table>;
fn get_row(self: Pin<&mut Table>) -> *mut Row;
type Row;
pub fn set_value(self: Pin<&mut Row>, value: &CxxString);
}
}
impl ffi::Table {
pub fn get_row_ref<'a>(self: Pin<&'a mut ffi::Table>) -> Pin<&'a mut ffi::Row> {
unsafe { Pin::new_unchecked(&mut *self.get_row()) }
}
}
fn main() {
let mut table = ffi::make_table();
let row = table.pin_mut().get_row_ref();
let_cxx_string!(hello="hello world");
row.set_value(&hello);
let_cxx_string!(hello2="bye world");
row.set_value(&hello2);
}
Note that:
- The
cxx
module requires that non-const C++ methods takePin<&mut T>
as their receiver - The C++
get_row
method returns a pointer, which I want to convert into a reference to the Row which has the same lifetime as the owningTable
object - that's what theget_row_ref
wrapper is for.
I have two problems:
Is it sound for me to call
Pin::new_unchecked
here? The documentation implies it is not:calling Pin::new_unchecked on an &'a mut T is unsafe because while you are able to pin it for the given lifetime 'a, you have no control over whether it is kept pinned once 'a ends
If that is not safe, how do I proceed?
This program fails to compile with the following error:
error[E0382]: use of moved value: `row` --> src/main.rs:41:2 | 34 | let row = table.pin_mut().get_row_ref(); | --- move occurs because `row` has type `Pin<&mut Row>`, which does not implement the `Copy` trait ... 38 | row.set_value(&hello); | --- value moved here ... 41 | row.set_value(&hello2); | ^^^ value used here after move
The first call to
set_value
consumes the pinned reference, and after that it can't be used again.&mut T
is notCopy
, soPin<&mut Row>
is notCopy
either.How do I set up the API so that the reference to
Row
can be used for multiple successive method calls (within the constraints established bycxx
)?
For those wanting to try it out:
# Cargo.toml
[dependencies]
cxx = "1.0.52"
[build-dependencies]
cxx-build = "1.0"
// build.rs
fn main() {
cxx_build::bridge("src/main.rs")
.flag("-std=c++17")
.include(".")
.compile("test");
}