Hey y'all, this is my first post. I'm very excited to finally be here.
I'm playing around with coroutines (on nightly) and ended up in a situation where I would like my coroutine to have mutable access to some data while it is running but not hold on to the reference when it is suspended. So, I came up with the idea of passing a mutable reference into the coroutine when resuming it and handing it back when yielding/returning. The following code compiles without error and works as intended:
type Coro<'a, T> =
Pin<Box<dyn Coroutine<&'a mut T, Yield = &'a mut T, Return = &'a mut T> + 'a>>;
type GenCoro<T> =
Pin<Box<dyn for<'a> Coroutine<&'a mut T, Yield = &'a mut T, Return = &'a mut T>>>;
fn coro_fn<'a>(_: PhantomData<&'a ()>) -> Coro<'a, String> {
Box::pin(
#[coroutine]
static |mut data: &mut String| {
while !data.is_empty() {
data = yield data;
data.pop();
}
data
},
)
}
(You can ignore the PhantomData
for now, it only becomes relevant later.)
However, the lifetime parameter 'a
of Coro
is a little annoying. If I'm not mistaken, the Coro
returned by coro_fn
can't actually hold on to any data with lifetime 'a
. Thus, I would love to generalise over the 'a
and the return type of coro_fn
to be GenCoro<T>
. The borrow checker doesn't seem to happy with this though. It complains about the implementation of Coroutine
not being general enough as it must implement the trait for all lifetimes but only does so for some specific anonymous lifetime.
My first question is whether transmuting the result of coro_fn
into GenCoro<T>
is safe? My intuition about parametricity (in 'a
), affine types, String
string being 'static
, and the coroutine closure being static
(as opposed to move
) tells me that this is safe indeed. Or did I miss anything?
In case this is safe, I would—of course—love to capture the pattern in a safe higher-order function. I came up with the following combinator:
fn generalize<T, F>(f: F) -> GenCoro<T>
where
T: 'static,
F: for<'a> Fn(PhantomData<&'a ()>) -> Coro<'a, T>,
F: 'static,
{
unsafe { std::mem::transmute::<_, GenCoro<T>>(f(PhantomData)) }
}
So, my second question is whether the transmute
in this function is always safe under the given constraints. Again, my intuition says "yes" but I'm well aware that I might be missing something. Is there actually a way to relax the constraints in order to make the function usable in more situations?
I'm very much looking forward to your answers/suggestions/etc. Many thanks!