Is it possible to switch pinned enum from one variant to another?

I have an enum like this:

#[pin_project::pin_project(project = InnerProj)]
enum Inner<R: Request> {
    Request(R),
    Future(#[pin] R::Send),
}

(playground)
And a function R -> R::Send, is it possible to turn Inner::Request into Inner::Future if the value is pinned (I have Pin<&mut Inner<_>>)?

In a non pinned case, I would use take_mut::take, but in this case, it's useless since I can't get &mut Inner<_>.

The only solution I could came up with is using pin_project's project_replace and a temporary variant:

#[pin_project::pin_project(project = InnerProj, project_replace = InnerRepl)]
enum Inner<R: Request> {
    Request(R),
    Future(#[pin] R::Send),
    Tmp,
}
let inner = self.as_mut().project_replace(Inner::Tmp);
let inner = match inner {
    InnerRepl::Request(req) => Inner::Future(req.send()),
    InnerRepl::Future(_) | InnerRepl::Tmp => unreachable!(),
};
self.as_mut().project_replace(inner);

(playground)
But this requires to write Inner::Tmp => unreachable!(), in every match which is really annoing.

A simple alternative is to use Option instead of peoject_replace: playground
Note that this may little increases the size of Inner if R is greater than R::Send. (project_replace allows small size optimizations like this.)

By the way, if you don't need the return value of project_replace, I recommend using Pin::set method instead:

self.set(inner);

This doesn't change much — it just replaces Inner::Tmp => unreachable!(), with .unwrap() :slight_smile:

In this case the issue is that you need the value already in the enum to construct the next value, and ultimately that's just an inherently unsafe operation. What happens if req.send() panics while you're moving the value? With take_mut, your process is aborted. Using a dummy value is one answer to the "what happens if it panics" question.

I recommend introducig a Done state that stores that the future is currently finished and using that as your dummy vaue. Then you can also move to that state when the future is done and get a fused future for free.

Note that if you really want to use take_mut, it can be done with an unsafe block.

1 Like

Yeah, that's a question. I was thinking if is it possible to implement a function like this:

fn take_pin<T>(pin: Pin<&mut T>, f: impl FnOnce(T) -> T) { ... }

That aborts on panic, just like take_mut? Or would this be unsound?

That's a little strange since 'done' state is already handled by the underling future. Thought I'll probably use this idea, thanks!

Your take_pin would indeed be unsound because it moves the T which has been pinned. If you used it while the enum was in the Future state, that would be bad. Of course, if the enum is in the Request state, no fields are pinned and hence it would be safe in that situation, but your take_pin method can't tell whether that's the case or not. You have to use an unsafe block to promise that you're in the later case, and not the former.

Having explicit Done states is pretty common.

1 Like