I'm trying to create the following design for a pinned, self-referential struct:
use std::future::Future;
use std::marker::PhantomPinned;
use std::pin::Pin;
struct Data {}
struct DataResult {}
async fn my_async(data: &Data) -> DataResult {
todo!()
}
/// Container for a pinned struct, where in inner future
/// contains a reference to self.data.
struct MyAsyncContainer<F: Future<Output = DataResult>> {
data: Data,
fut: Option<F>,
unpin: PhantomPinned,
}
impl<F: Future<Output = DataResult>> MyAsyncContainer<F> {
fn new(data: Data) -> Self {
Self {
data,
fut: None,
unpin: PhantomPinned,
}
}
fn start<'a, A>(self: Pin<&'a mut Self>, func: A)
where
A: FnOnce(&'a Data) -> F,
{
let this = unsafe { self.get_unchecked_mut() };
// ????
let fut = func(&this.data);
// self is pinned, but fut is not (yet),
// so this should be safe
this.fut = Some(fut);
}
}
fn main() {
let data = Data {};
let mut container = MyAsyncContainer::new(data);
let pinned = unsafe { Pin::new_unchecked(&mut container) };
pinned.start(my_async);
}
The start
function compiles, but when I try to compile the main
function, I get the following error:
error[E0597]: `container` does not live long enough
--> src/main.rs:48:46
|
48 | let pinned = unsafe { Pin::new_unchecked(&mut container) };
| ^^^^^^^^^^^^^^ borrowed value does not live long enough
49 | pinned.start(my_async);
50 | }
| -
| |
| `container` dropped here while still borrowed
| borrow might be used here, when `container` is dropped and runs the destructor for type `MyAsyncContainer<impl std::future::Future>`
Is there a way to make a self-referential struct like this work correctly?
I did consider just using an async function, like so:
async fn my_async(data: &Data) -> DataResult {
todo!()
}
async fn data_wrapper(data: Data) -> DataResult {
my_async(&data).await
}
The problem is that I need the future type to be nameable, because it's being embedded in another struct as part of a larger async library.
I've read How to use the Pin struct with self-referential structures? but it didn't answer my question, though the points about raw pointers were helpful. The key difference here that I don't think is covered by those examples is the my_async
function, which does need to take a piece of data by reference. I can't pass a pointer into it, and it's not clear to me how to transition the pointer to a reference with the right lifetime. In particular, my_async
needs to be prevented from operating on &'static data
and leaking the reference.