2

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.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Oliver
  • 353
  • 1
  • 2
  • 11
  • I think the signature you want is actually: `unsafe fn foo<'a, T>(func: impl FnOnce() -> T + 'a) -> impl FnOnce() -> T + 'static`. – Peter Hall Sep 20 '18 at 12:49
  • 1
    But I'm not sure that it will work because the concrete type of the `impl` in the argument is determined by the caller, but the `impl` in the return position is determined by the function. Yet somehow they have to be the same? I don't think you can do that. – Peter Hall Sep 20 '18 at 12:52
  • And if you try to transmute, you will need to say what type to transmute to, but you can't actually name the type of a closure. – Peter Hall Sep 20 '18 at 12:57
  • This is such an unusual request that I have to imagine you've overlooked some other way to achieve whatever it is you're really trying to do. Why don't you ask a question about the underlying problem (*spawning threads where the lifetime restrictions are enforced by other means*)? Scoped threads, for example, can solve a lot of lifetime problems ([relevant Q&A](https://stackoverflow.com/questions/32750829/how-can-i-pass-a-reference-to-a-stack-variable-to-a-thread)). – trent Sep 20 '18 at 13:25
  • _"This is such an unusual request..."_ — agreed. See the summary at the end of my answer. – Peter Hall Sep 20 '18 at 13:27
  • Possible duplicate of [Casting away lifetime constraints?](https://stackoverflow.com/questions/31089768/casting-away-lifetime-constraints) – trent Oct 19 '18 at 15:52

1 Answers1

2

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.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Thanks for your answer, using boxed closures as trait objects works reasonably well for the closure, but my problem is primarily with the closure's return type. I also don't really need to trick the borrow checker so much as the type system, as the type system, as lifetime bounds appear to be inseparably linked to a (generic) type once they are established. You are probably right, however, that there is not really a good way to do this. – Oliver Sep 20 '18 at 20:05
  • My intentions is precisely to implement scoped threads. The signature for stdlib thread spawn is `pub fn spawn(f: F) -> JoinHandle where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static` and I tried to find a way to trick the type system into letting me use a closure/return type with less strict lifetime bounds. After having taken an in depth look at some scoped thread implementations I found out they have to jump through a lot of hoops to get this kind of thing to work and I hoped to find an easier way. – Oliver Sep 20 '18 at 20:08