对FFI对象的固定引用,该对象由调用接收它的方法使用



我正在编写一个外部函数接口(ffi),以将预先存在的C++库的API公开给我正在编写的一些新Rust代码。我使用的是Rustcxx模块。

我遇到了一些与Pin有关的问题(我不得不承认,我对这个话题还没有完全掌握)。

我的C++模块有一个API,它从拥有某些包含对象的主对象中公开指向这些包含对象的指针。这里有一个人为的例子:

// 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>();
}

这个想法是创建一个Table对象,然后它允许您获得指向它的Row的指针,这样您就可以操作它

我创建Rust FFI的尝试如下:

// 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);

}

注意:

  • cxx模块要求非常量C++方法将Pin<&mut T>作为其接收器
  • C++get_row方法返回一个指针,我想将其转换为对Row的引用,该Row的生存期与所属Table对象的生存期相同——这就是get_row_ref包装器的作用

我有两个问题:

  1. 我在这里打Pin::new_unchecked合适吗?文件暗示它不是:

    在&'上调用Pin::new_uncheckedmut T是不安全的,因为当你能够在给定的生命周期内固定它,你无法控制是否一旦结束,它就会被固定

    如果这不安全,我该如何继续?

  2. 此程序编译失败,出现以下错误:

    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
    

    set_value的第一次调用消耗了固定引用,之后就不能了再次使用。&mut T不是Copy,所以Pin<&mut Row>也不是Copy

    如何设置API,以便对Row的引用可以用于多个连续的方法调用(在cxx建立的约束内)?

对于那些想要尝试的人:

# 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. 我在这里呼叫Pin::new_unchecked是否正确

是的,听起来不错。在这种情况下,我们知道Row被固定是因为:

  1. 固定Table
  2. CCD_ 21被内联存储在CCD_
  3. C++的移动语义本质上意味着每个C++对象都是"移动"对象;被钉住";无论如何
  1. 此程序编译失败,并出现以下错误:

在对普通可变引用(&mut T)调用方法时,编译器会隐式执行重调用,以避免移动可变引用,因为&mut T不是Copy。不幸的是,这个编译器";魔术;没有扩展到Pin<&mut T>(它也不是Copy),所以我们必须显式地重新调用。

重新借款最简单的方法是使用Pin::as_mut()。这个用例甚至在文档中被调用:

当对使用固定类型的函数执行多个调用时,此方法非常有用。

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

在最后一次使用row时使用as_mut()并不是严格必要的,但始终如一地使用它可能更清楚。当使用优化编译时,该函数可能无论如何都是noop(对于Pin<&mut T>)。

如何设置API,以便对Row的引用可以用于多个连续的方法调用(在cxx建立的约束范围内)?

如果要隐藏as_mut()的,可以添加一个接受&mut Pin<&mut ffi::Row>并执行as_mut()调用的方法。(注意,as_mut()是在&mut Pin<P>上定义的,因此编译器将插入外部&mut的重箭头。)是的,这意味着现在有两个间接级别。

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

最新更新