通过调用接收它的方法使用的 FFI 对象的固定引用
Pinned reference to FFI object consumed by calling methods that receive it
我正在编写一个外部函数接口 (ffi) 以将预先存在的 C++ 库的 API 暴露给我正在编写的一些新 Rust 代码。为此,我正在使用 Rust cxx
模块。
我 运行 遇到了一些与 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
方法 returns 一个指针,我想将其转换为对 Row 的引用,该行与拥有的 Table
对象具有相同的生命周期 - 这就是get_row_ref
包装器用于。
我有两个问题:
我在这里打电话给Pin::new_unchecked
可以吗?文档暗示它不是:
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
如果那不安全,我该如何进行?
此程序编译失败,出现以下错误:
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");
}
- Is it sound for me to call
Pin::new_unchecked
here?
是的,很好听。在这种情况下,我们知道 Row
已固定,因为:
Table
已固定;
Row
内联存储在 Table
中;
- C++ 的移动语义本质上意味着每个 C++ 对象无论如何都是“固定”的。
- This program fails to compile with the following error:
当您在普通可变引用 (&mut T
) 上调用方法时,编译器会隐式执行 重新借用 以避免移动可变引用,因为 &mut T
不是 Copy
。不幸的是,这个编译器“魔法”没有扩展到 Pin<&mut T>
(也不是 Copy
),所以我们必须明确地重新借用。
最简单的再借方法是使用Pin::as_mut()
。这个用例甚至在文档中被调用:
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);
}
在最后一次使用 row
时使用 as_mut()
并不是绝对必要的,但始终如一地应用它可能会更清楚。当使用优化编译时,这个函数可能是一个 noop(对于 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
)?
如果您想隐藏 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);
}
我正在编写一个外部函数接口 (ffi) 以将预先存在的 C++ 库的 API 暴露给我正在编写的一些新 Rust 代码。为此,我正在使用 Rust cxx
模块。
我 运行 遇到了一些与 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
方法 returns 一个指针,我想将其转换为对 Row 的引用,该行与拥有的Table
对象具有相同的生命周期 - 这就是get_row_ref
包装器用于。
我有两个问题:
我在这里打电话给
Pin::new_unchecked
可以吗?文档暗示它不是: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
如果那不安全,我该如何进行?
此程序编译失败,出现以下错误:
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");
}
- Is it sound for me to call
Pin::new_unchecked
here?
是的,很好听。在这种情况下,我们知道 Row
已固定,因为:
Table
已固定;Row
内联存储在Table
中;- C++ 的移动语义本质上意味着每个 C++ 对象无论如何都是“固定”的。
- This program fails to compile with the following error:
当您在普通可变引用 (&mut T
) 上调用方法时,编译器会隐式执行 重新借用 以避免移动可变引用,因为 &mut T
不是 Copy
。不幸的是,这个编译器“魔法”没有扩展到 Pin<&mut T>
(也不是 Copy
),所以我们必须明确地重新借用。
最简单的再借方法是使用Pin::as_mut()
。这个用例甚至在文档中被调用:
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);
}
在最后一次使用 row
时使用 as_mut()
并不是绝对必要的,但始终如一地应用它可能会更清楚。当使用优化编译时,这个函数可能是一个 noop(对于 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 bycxx
)?
如果您想隐藏 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);
}