我正在尝试在 Rust 中实现一个自引用结构。
我正在写一个基于另一个板条箱的板条箱。底层板条箱公开了一个类型
Transaction<'a>
,它需要对 Client
对象的可变引用:
struct Client;
struct Transaction<'a> {
client: &'a mut Client
}
构造
Transaction
的唯一方法是调用客户端对象上的方法。该函数看起来像这样:
impl Client {
async fn transaction<'a>(&'a mut self) -> Transaction<'a> {
// ...
}
}
现在我想公开一个允许这样的API:
let transaction = Transaction::new().await;
当然,如果不通过
&mut Client
,这是行不通的。
现在我的想法是创建一个包装类型,它拥有一个
Client
以及一个 Transaction
并引用所述客户端。
我将使用
unsafe
创建一个可变引用,该引用可用于创建 Transaction
,同时仍然能够将 Client
对象传递给我的结构。我会将 Client
固定在堆上,以确保引用不会变得无效:
struct MyTransaction<'a> {
client: Pin<Box<Client>>,
inner: Transaction<'a>
}
impl<'a> MyTransaction<'a> {
async fn new() -> MyTransaction<'a> {
// ... fetch a new client object
let pin = Box::pin(client);
let pointer = &*pin as *const Client as *mut Client;
// convert raw pointer to mutable reference
// to create new Transaction
let inner = unsafe {
&mut *pointer
}.transaction().await;
Self {
inner,
client
}
}
}
但是,当我运行此代码时,我的测试失败并显示以下错误消息:
error: test failed
Caused by:
process didn't exit successfully: `/path/to/test` (signal: 11, SIGSEGV: invalid memory reference)
这让我有些惊讶,因为我认为通过固定
client
对象我可以确保它不会被移动。因此,我推断,只要它的寿命不超过客户端,对它的指针/引用就不应该变得无效。我认为情况不可能是这样,因为只有当 MyTransaction
被删除时客户端才会被删除,这也会删除保存引用的 Transaction
。我也认为移动 MyTransaction
应该是可能的。
我做错了什么?预先感谢!
这里的问题是字段上的隐式删除顺序,在 Rust 中,该顺序是声明顺序,这意味着您的
client
在 inner
之前被删除,因此当 inner
被删除并使用它对 client
的引用时,该引用悬空并导致您看到 SIGSEGV。
一个简单的解决方法就是重新排序它们:
struct MyTransaction<'a> {
inner: Transaction<'a>
client: Pin<Box<Client>>,
}
对于更复杂的场景,请参阅强制删除结构体字段的顺序