Oliver
Oliver

Reputation: 385

Is there a way to express "same" generic type with different lifetime bound?

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

Answers (1)

Peter Hall
Peter Hall

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

Related Questions