Reputation: 385
Consider the following (incomplete) function signature:
unsafe fn foo<'a, T: 'a>(func: impl FnOnce() -> T + 'a) -> ...
Is there a way to (unsafely of course) transmute
the input function so, that it becomes impl FnOnce() -> S + 'static
where S is the same type as T but with S: 'static
.
I know it is possible to transmute the lifetime bounds on the closure itself by using a boxed trait (FnBox
) and then calling transmute on the box. However, this does not affect the return type (T
). As far as I understand, T: 'a
and T: 'static
are different types as far as the type system goes. So I wonder if it is even possible to express this in Rust.
I suppose the signature would have to look like this (ignoring the lifetime bounds on the closure itself):
unsafe fn<'a, T, S>(func: impl FnOnce() -> T) -> impl FnOnce() -> S
where
T: 'a,
S: 'static`
but then how do you call this function without the specification that T
and S
are identical except for their lifetime bound.
Disclaimer I am aware that tinkering with lifetime bounds is generally a bad idea, however this is for spawning threads where the lifetime restrictions are enforced by other means.
Upvotes: 2
Views: 428
Reputation: 58805
If you just wanted to do this with simple types this would be straightforward, but there are a number of obstacles to what you are trying. I'll explain them in the order that I came across them while trying to find an answer.
First, you can't implement this with impl trait
types, because the function itself must choose the concrete implementation that it's going to return, but it can't because the implementation will always be based on the choice of the type of the argument func
from the caller. This rules out the "natural" type of:
unsafe fn foo<'a, T>(func: impl FnOnce() -> T + 'a) -> impl FnOnce() -> T + 'static
And leads to something more like:
unsafe fn foo<'a, T, F, G>(func: F) -> G
where
F: FnOnce() -> + 'a,
G: FnOnce() -> + 'static,
But how does the caller know what type G
needs to be?
If you try to use mem::transmute
to cheat the borrow checker, you will need to tell it what to transmute into. The problem is that you only know the type is (for example) impl FnOnce() -> T + 'static
, but you can't actually write down the concrete type of a closure, so this isn't going to work either.
So I think the answer is to Box
the result. This might sound unsatisfactory, but it gets worse! While it's possible to create a Box<dyn FnOnce()>
it is currently impossible to call that function later, which means you have to make another compromise, which is to upgrade from FnOnce
to Fn
.
use std::mem;
unsafe fn foo<'a, T>(func: impl Fn() -> T + 'a) -> Box<dyn Fn() -> T + 'static> {
let boxed: Box<dyn Fn() -> T + 'a> = Box::new(func);
mem::transmute(boxed)
}
In summary, perhaps you should take a step back and find a different problem to solve, instead of this one.
Upvotes: 2