我正在编写一个外部函数接口(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
包装器的作用
我有两个问题:
我在这里打
Pin::new_unchecked
合适吗?文件暗示它不是:在&'上调用Pin::new_uncheckedmut T是不安全的,因为当你能够在给定的生命周期内固定它,你无法控制是否一旦结束,它就会被固定
如果这不安全,我该如何继续?
此程序编译失败,出现以下错误:
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");
}
- 我在这里呼叫
Pin::new_unchecked
是否正确
是的,听起来不错。在这种情况下,我们知道Row
被固定是因为:
- 固定
Table
- CCD_ 21被内联存储在CCD_
- C++的移动语义本质上意味着每个C++对象都是"移动"对象;被钉住";无论如何
- 此程序编译失败,并出现以下错误:
在对普通可变引用(&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);
}