1
votes

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 take Pin<&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 owning Table object - that's what the get_row_ref wrapper is for.

I have two problems:

  1. 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?

  2. 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 not Copy, so Pin<&mut Row> is not Copy 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 by cxx)?

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");
}
1

1 Answers

1
votes
  1. Is it sound for me to call Pin::new_unchecked here?

Yes, it's sound. In this context, we know the Row is pinned because:

  1. The Table is pinned;
  2. The Row is stored inline in the Table;
  3. C++'s move semantics essentially means every C++ object is "pinned" anyway.
  1. This program fails to compile with the following error:

When you call a method on a normal mutable reference (&mut T), the compiler implicitly performs a reborrow in order to avoid moving the mutable reference, because &mut T is not Copy. Unfortunately, this compiler "magic" doesn't extend to Pin<&mut T> (which is not Copy either), so instead we must reborrow explicitly.

The easiest way to reborrow is to use Pin::as_mut(). This use case is even called out in the documentation:

This method is useful when doing multiple calls to functions that consume the pinned type.

fn main() {
    let mut table = ffi::make_table();

    let mut row = table.pin_mut().get_row_ref();

    let_cxx_string!(hello="hello world");
    row.as_mut().set_value(&hello);

    let_cxx_string!(hello2="bye world");
    row.as_mut().set_value(&hello2);
}

The use of as_mut() on the last use of row is not strictly necessary, but applying it consistently is probably clearer. When compiled with optimizations, this function is probably a noop anyway (for Pin<&mut T>).

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 by cxx)?

If you want to hide the as_mut()'s, you could add a method that accepts a &mut Pin<&mut ffi::Row> and does the as_mut() call. (Note that as_mut() is defined on &mut Pin<P>, so the compiler will insert a reborrow of the outer &mut.) Yes, this means there are now two levels of indirection.

impl ffi::Row {
    pub fn set_value2(self: &mut Pin<&mut ffi::Row>, value: &cxx::CxxString) {
        self.as_mut().set_value(value)
    }
}

fn main() {
    let mut table = ffi::make_table();

    let mut row = table.pin_mut().get_row_ref();

    let_cxx_string!(hello="hello world");
    row.set_value2(&hello);

    let_cxx_string!(hello2="bye world");
    row.set_value2(&hello2);
}